视图在旋转后无法在 x 和 y 轴上正确移动
View does not move correctly in x and y axis after it's been rotated
我目前正在一个视图组中编写代码,该视图组有一个框架布局作为子视图,其中有一个视图。这个视图组负责通过在其中实现 MotionEvent 来旋转、缩放、移动视图。到目前为止,我已经能够在其中实现旋转、缩放、移动,它们工作得很好,直到我旋转整个视图组,之后它似乎没有按预期移动(顺便说一句,缩放没问题)。
我想问题是在我将视图旋转 180 度(或什至一点)后,x 和 y 位置有点互换并且它不再起作用(直到它旋转回其原始位置).提前谢谢你。
if rotation is not applied image
if rotation is applied image
运动事件代码:
private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
// For scaling
scaleDetector.onTouchEvent(event)
val pointerCount = event.pointerCount
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// if view is in editing state
if (drawFrame) {
// Save the initial x and y of that touched point
initialX = event.x
initialY = event.y
}
performClick()
}
MotionEvent.ACTION_MOVE -> {
// If view is in editing state (got clicked)
if (drawFrame) {
/* Moving the view by touch */
// and if there is only 1 pointer on the screen
if (pointerCount == 1) {
// Move the view
v.x += event.x - initialX
v.y += event.y - initialY
// Don't let the view go beyond the phone's display and limit it's x and y
(parent as FrameLayout).let { parent ->
val parentWidth = parent.width
val parentHeight = parent.height
if ((v.x + v.width) >= parentWidth) v.x =
(parentWidth - v.width).toFloat()
if ((v.y + v.height) >= parentHeight) v.y =
(parentHeight - v.height).toFloat()
if (v.x <= parent.x) v.x = parent.x
if (v.y <= parent.y) v.y = parent.y
}
}
/* Rotating the view by touch */
// If there are total of two pointer on the screen
if (pointerCount == 2) {
rotatedDegree =
event.run { /* <----- I think problem is in that code block */
// Get the first pointer x and y
val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
// Get the second pointer x and y
val (secondX, secondY) = getPointerInfoAt(getPointerId(1))
// Calculate the difference between those points
val deltaX = firstX - secondX
val deltaY = secondY - firstY
// Get the total degree that view got rotated
val totalDegreeOfRotation =
Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()
Log.i(
"MotionEvent",
"Total degree of rotation is $totalDegreeOfRotation " +
"first x : "
)
totalDegreeOfRotation
}
// Rotate the ViewGroup
rotation += rotatedDegree
}
}
}
}
true
}
缩放代码:
private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
if (!isChildMeasured) {
initialScaleHeight = child.height
initialScaleWidth = child.width
isChildMeasured = !isChildMeasured
}
Log.i(
"SCALE",
"onScaleBegin: InitialScaleWidth $initialScaleWidth || InitialScaleHeigh $initialScaleHeight"
)
return true
}
override fun onScale(detector: ScaleGestureDetector?): Boolean {
scaleFactor *= detector!!.scaleFactor
scaleFactor = max(0.1f, min(scaleFactor, 2.0f))
var childTextSize = child.textSize
childTextSize *= scaleFactor
if (childTextSize < 18f) childTextSize = 18f
if (childTextSize > 85f) childTextSize = 85f
child.textSize = childTextSize
// In views we should only change the property that determines the view size, not the actual view size
requestLayout()
return true
}
}
EditableView.kt(全部代码):
class EditableView(context: Context, attr: AttributeSet?) : ViewGroup(context, attr) {
constructor(context: Context) : this(context, null)
private var drawFrame: Boolean = true
private var scaleFactor = 1f
private var initialScaleWidth = 0
private var initialScaleHeight = 0
private var rotatedDegree = 0f
private val child: TextView
get() =
mainViewHolder.children.first() as TextView
private var isChildMeasured: Boolean = false
private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
if (!isChildMeasured) {
initialScaleHeight = child.height
initialScaleWidth = child.width
isChildMeasured = !isChildMeasured
}
Log.i(
"SCALE",
"onScaleBegin: InitialScaleWidth $initialScaleWidth || InitialScaleHeigh $initialScaleHeight"
)
return true
}
override fun onScale(detector: ScaleGestureDetector?): Boolean {
scaleFactor *= detector!!.scaleFactor
scaleFactor = max(0.1f, min(scaleFactor, 2.0f))
var childTextSize = child.textSize
childTextSize *= scaleFactor
if (childTextSize < 18f) childTextSize = 18f
if (childTextSize > 85f) childTextSize = 85f
child.textSize = childTextSize
// In views we should only change the property that determines the view size, not the actual view size
requestLayout()
return true
}
}
private val scaleDetector = ScaleGestureDetector(context, scaleListener)
private var initialX = 0f
private var initialY = 0f
private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
// For scaling
scaleDetector.onTouchEvent(event)
val pointerCount = event.pointerCount
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// if view is in editing state
if (drawFrame) {
// Save the initial x and y of that touched point
initialX = event.x
initialY = event.y
}
performClick()
}
MotionEvent.ACTION_MOVE -> {
// If view is in editing state (got clicked)
if (drawFrame) {
/* Moving the view by touch */
// and if there is only 1 pointer on the screen
if (pointerCount == 1) {
// Move the view
v.x += event.x - initialX
v.y += event.y - initialY
// Don't let the view go beyond the phone's display and limit it's x and y
(parent as FrameLayout).let { parent ->
val parentWidth = parent.width
val parentHeight = parent.height
if ((v.x + v.width) >= parentWidth) v.x =
(parentWidth - v.width).toFloat()
if ((v.y + v.height) >= parentHeight) v.y =
(parentHeight - v.height).toFloat()
if (v.x <= parent.x) v.x = parent.x
if (v.y <= parent.y) v.y = parent.y
}
}
/* Rotating the view by touch */
// If there are total of two pointer on the screen
if (pointerCount == 2) {
rotatedDegree =
event.run { /* <----- I think problem is in that code block */
// Get the first pointer x and y
val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
// Get the second pointer x and y
val (secondX, secondY) = getPointerInfoAt(getPointerId(1))
// Calculate the difference between those points
val deltaX = firstX - secondX
val deltaY = secondY - firstY
// Get the total degree that view got rotated
val totalDegreeOfRotation =
Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()
Log.i(
"MotionEvent",
"Total degree of rotation is $totalDegreeOfRotation " +
"first x : "
)
totalDegreeOfRotation
}
// Rotate the ViewGroup
rotation += rotatedDegree
}
}
}
}
true
}
private val mainViewHolder = FrameLayout(context).apply {
layoutParams =
FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
)
}
private val mainFrameBoundaryPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
strokeWidth = 2.dp
style = Paint.Style.STROKE
}
private val frameLayoutRectangle = RectF()
init {
setOnTouchListener(motionEventHandler)
setWillNotDraw(false)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
addView(mainViewHolder)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChild(mainViewHolder, widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(
resolveSize(
mainViewHolder.measuredWidth,
widthMeasureSpec
),
resolveSize(mainViewHolder.height, heightMeasureSpec)
)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var x = 0
mainViewHolder.layout(x, t, x + mainViewHolder.measuredWidth, mainViewHolder.measuredHeight)
x += mainViewHolder.measuredWidth
frameLayoutRectangle.set(
0f, 0f,
x.toFloat(),
mainViewHolder.measuredHeight.toFloat()
)
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
if (drawFrame)
canvas!!.apply {
drawRoundRect(frameLayoutRectangle, 2.dp, 2.dp, mainFrameBoundaryPaint)
}
}
fun addToFrame(view: View) {
// Let the canvas draw it's rectangle meaning that view is getting edited
drawFrame = true
// Add the view that's going to get edited to the FrameLayout
mainViewHolder.addView(view)
}
fun showFrameAroundView() {
// Show the rectangle frame around the view
if (!drawFrame) {
drawFrame = true
invalidate()
}
}
fun hideFrameAroundView() {
// Hide the rectangle around the view (meaning it's not longer in editing state)
if (drawFrame) {
drawFrame = false
invalidate()
}
}
fun doesHaveChild(): Boolean {
return childCount > 0
}
}
如果您能帮助我更好地实现该场景,我将不胜感激。
经过一天的反复试验和网上搜索,我终于找到了解决方案。
问题是我在计算中没有使用原始的 x 和 y。
这是修复它的 MotionEvent 处理程序代码:
private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
// For scaling
scaleDetector.onTouchEvent(event)
val pointerCount = event.pointerCount
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (drawFrame) {
initialX = v.x - event.rawX
initialY = v.y - event.rawY
}
performClick()
}
MotionEvent.ACTION_MOVE -> {
// If view is in editing state (got clicked)
if (drawFrame) {
/* Moving the view by touch */
// and if there is only 1 pointer on the screen
if (pointerCount == 1) {
val viewParent = parent as ViewGroup
// Move the view
v.x = event.rawX + initialX
v.y = event.rawY + initialY
// Don't let the view go beyond the phone's display and limit it's x and y
viewParent.let { parent ->
val parentHeight = parent.height
if ((v.y + v.height) >= parentHeight) v.y =
(parentHeight - v.height).toFloat()
if (v.y <= parent.y) v.y = parent.y
}
}
/* Rotating the view by touch */
// If there are total of two pointer on the screen
if (pointerCount == 2) {
rotatedDegree =
event.run { /* <----- I think problem is in that code block */
// Get the first pointer x and y
val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
// Get the second pointer x and y
val (secondX, secondY) = getPointerInfoAt(getPointerId(1))
// Calculate the difference between those points
val deltaX = firstX - secondX
val deltaY = secondY - firstY
// Get the total degree that view got rotated
val totalDegreeOfRotation =
Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()
Log.i(
"MotionEvent",
"Total degree of rotation is $totalDegreeOfRotation " +
"first x : "
)
totalDegreeOfRotation
}
// Rotate the ViewGroup
rotation += rotatedDegree
}
}
}
}
true
}
我目前正在一个视图组中编写代码,该视图组有一个框架布局作为子视图,其中有一个视图。这个视图组负责通过在其中实现 MotionEvent 来旋转、缩放、移动视图。到目前为止,我已经能够在其中实现旋转、缩放、移动,它们工作得很好,直到我旋转整个视图组,之后它似乎没有按预期移动(顺便说一句,缩放没问题)。
我想问题是在我将视图旋转 180 度(或什至一点)后,x 和 y 位置有点互换并且它不再起作用(直到它旋转回其原始位置).提前谢谢你。
if rotation is not applied image
if rotation is applied image
运动事件代码:
private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
// For scaling
scaleDetector.onTouchEvent(event)
val pointerCount = event.pointerCount
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// if view is in editing state
if (drawFrame) {
// Save the initial x and y of that touched point
initialX = event.x
initialY = event.y
}
performClick()
}
MotionEvent.ACTION_MOVE -> {
// If view is in editing state (got clicked)
if (drawFrame) {
/* Moving the view by touch */
// and if there is only 1 pointer on the screen
if (pointerCount == 1) {
// Move the view
v.x += event.x - initialX
v.y += event.y - initialY
// Don't let the view go beyond the phone's display and limit it's x and y
(parent as FrameLayout).let { parent ->
val parentWidth = parent.width
val parentHeight = parent.height
if ((v.x + v.width) >= parentWidth) v.x =
(parentWidth - v.width).toFloat()
if ((v.y + v.height) >= parentHeight) v.y =
(parentHeight - v.height).toFloat()
if (v.x <= parent.x) v.x = parent.x
if (v.y <= parent.y) v.y = parent.y
}
}
/* Rotating the view by touch */
// If there are total of two pointer on the screen
if (pointerCount == 2) {
rotatedDegree =
event.run { /* <----- I think problem is in that code block */
// Get the first pointer x and y
val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
// Get the second pointer x and y
val (secondX, secondY) = getPointerInfoAt(getPointerId(1))
// Calculate the difference between those points
val deltaX = firstX - secondX
val deltaY = secondY - firstY
// Get the total degree that view got rotated
val totalDegreeOfRotation =
Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()
Log.i(
"MotionEvent",
"Total degree of rotation is $totalDegreeOfRotation " +
"first x : "
)
totalDegreeOfRotation
}
// Rotate the ViewGroup
rotation += rotatedDegree
}
}
}
}
true
}
缩放代码:
private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
if (!isChildMeasured) {
initialScaleHeight = child.height
initialScaleWidth = child.width
isChildMeasured = !isChildMeasured
}
Log.i(
"SCALE",
"onScaleBegin: InitialScaleWidth $initialScaleWidth || InitialScaleHeigh $initialScaleHeight"
)
return true
}
override fun onScale(detector: ScaleGestureDetector?): Boolean {
scaleFactor *= detector!!.scaleFactor
scaleFactor = max(0.1f, min(scaleFactor, 2.0f))
var childTextSize = child.textSize
childTextSize *= scaleFactor
if (childTextSize < 18f) childTextSize = 18f
if (childTextSize > 85f) childTextSize = 85f
child.textSize = childTextSize
// In views we should only change the property that determines the view size, not the actual view size
requestLayout()
return true
}
}
EditableView.kt(全部代码):
class EditableView(context: Context, attr: AttributeSet?) : ViewGroup(context, attr) {
constructor(context: Context) : this(context, null)
private var drawFrame: Boolean = true
private var scaleFactor = 1f
private var initialScaleWidth = 0
private var initialScaleHeight = 0
private var rotatedDegree = 0f
private val child: TextView
get() =
mainViewHolder.children.first() as TextView
private var isChildMeasured: Boolean = false
private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
if (!isChildMeasured) {
initialScaleHeight = child.height
initialScaleWidth = child.width
isChildMeasured = !isChildMeasured
}
Log.i(
"SCALE",
"onScaleBegin: InitialScaleWidth $initialScaleWidth || InitialScaleHeigh $initialScaleHeight"
)
return true
}
override fun onScale(detector: ScaleGestureDetector?): Boolean {
scaleFactor *= detector!!.scaleFactor
scaleFactor = max(0.1f, min(scaleFactor, 2.0f))
var childTextSize = child.textSize
childTextSize *= scaleFactor
if (childTextSize < 18f) childTextSize = 18f
if (childTextSize > 85f) childTextSize = 85f
child.textSize = childTextSize
// In views we should only change the property that determines the view size, not the actual view size
requestLayout()
return true
}
}
private val scaleDetector = ScaleGestureDetector(context, scaleListener)
private var initialX = 0f
private var initialY = 0f
private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
// For scaling
scaleDetector.onTouchEvent(event)
val pointerCount = event.pointerCount
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// if view is in editing state
if (drawFrame) {
// Save the initial x and y of that touched point
initialX = event.x
initialY = event.y
}
performClick()
}
MotionEvent.ACTION_MOVE -> {
// If view is in editing state (got clicked)
if (drawFrame) {
/* Moving the view by touch */
// and if there is only 1 pointer on the screen
if (pointerCount == 1) {
// Move the view
v.x += event.x - initialX
v.y += event.y - initialY
// Don't let the view go beyond the phone's display and limit it's x and y
(parent as FrameLayout).let { parent ->
val parentWidth = parent.width
val parentHeight = parent.height
if ((v.x + v.width) >= parentWidth) v.x =
(parentWidth - v.width).toFloat()
if ((v.y + v.height) >= parentHeight) v.y =
(parentHeight - v.height).toFloat()
if (v.x <= parent.x) v.x = parent.x
if (v.y <= parent.y) v.y = parent.y
}
}
/* Rotating the view by touch */
// If there are total of two pointer on the screen
if (pointerCount == 2) {
rotatedDegree =
event.run { /* <----- I think problem is in that code block */
// Get the first pointer x and y
val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
// Get the second pointer x and y
val (secondX, secondY) = getPointerInfoAt(getPointerId(1))
// Calculate the difference between those points
val deltaX = firstX - secondX
val deltaY = secondY - firstY
// Get the total degree that view got rotated
val totalDegreeOfRotation =
Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()
Log.i(
"MotionEvent",
"Total degree of rotation is $totalDegreeOfRotation " +
"first x : "
)
totalDegreeOfRotation
}
// Rotate the ViewGroup
rotation += rotatedDegree
}
}
}
}
true
}
private val mainViewHolder = FrameLayout(context).apply {
layoutParams =
FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
)
}
private val mainFrameBoundaryPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
strokeWidth = 2.dp
style = Paint.Style.STROKE
}
private val frameLayoutRectangle = RectF()
init {
setOnTouchListener(motionEventHandler)
setWillNotDraw(false)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
addView(mainViewHolder)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChild(mainViewHolder, widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(
resolveSize(
mainViewHolder.measuredWidth,
widthMeasureSpec
),
resolveSize(mainViewHolder.height, heightMeasureSpec)
)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var x = 0
mainViewHolder.layout(x, t, x + mainViewHolder.measuredWidth, mainViewHolder.measuredHeight)
x += mainViewHolder.measuredWidth
frameLayoutRectangle.set(
0f, 0f,
x.toFloat(),
mainViewHolder.measuredHeight.toFloat()
)
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
if (drawFrame)
canvas!!.apply {
drawRoundRect(frameLayoutRectangle, 2.dp, 2.dp, mainFrameBoundaryPaint)
}
}
fun addToFrame(view: View) {
// Let the canvas draw it's rectangle meaning that view is getting edited
drawFrame = true
// Add the view that's going to get edited to the FrameLayout
mainViewHolder.addView(view)
}
fun showFrameAroundView() {
// Show the rectangle frame around the view
if (!drawFrame) {
drawFrame = true
invalidate()
}
}
fun hideFrameAroundView() {
// Hide the rectangle around the view (meaning it's not longer in editing state)
if (drawFrame) {
drawFrame = false
invalidate()
}
}
fun doesHaveChild(): Boolean {
return childCount > 0
}
}
如果您能帮助我更好地实现该场景,我将不胜感激。
经过一天的反复试验和网上搜索,我终于找到了解决方案。 问题是我在计算中没有使用原始的 x 和 y。
这是修复它的 MotionEvent 处理程序代码:
private val motionEventHandler: (view: View, event: MotionEvent) -> Boolean = { v, event ->
// For scaling
scaleDetector.onTouchEvent(event)
val pointerCount = event.pointerCount
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (drawFrame) {
initialX = v.x - event.rawX
initialY = v.y - event.rawY
}
performClick()
}
MotionEvent.ACTION_MOVE -> {
// If view is in editing state (got clicked)
if (drawFrame) {
/* Moving the view by touch */
// and if there is only 1 pointer on the screen
if (pointerCount == 1) {
val viewParent = parent as ViewGroup
// Move the view
v.x = event.rawX + initialX
v.y = event.rawY + initialY
// Don't let the view go beyond the phone's display and limit it's x and y
viewParent.let { parent ->
val parentHeight = parent.height
if ((v.y + v.height) >= parentHeight) v.y =
(parentHeight - v.height).toFloat()
if (v.y <= parent.y) v.y = parent.y
}
}
/* Rotating the view by touch */
// If there are total of two pointer on the screen
if (pointerCount == 2) {
rotatedDegree =
event.run { /* <----- I think problem is in that code block */
// Get the first pointer x and y
val (firstX, firstY) = getPointerInfoAt(getPointerId(0))
// Get the second pointer x and y
val (secondX, secondY) = getPointerInfoAt(getPointerId(1))
// Calculate the difference between those points
val deltaX = firstX - secondX
val deltaY = secondY - firstY
// Get the total degree that view got rotated
val totalDegreeOfRotation =
Math.toDegrees(atan2(deltaX, deltaY).toDouble()).toFloat()
Log.i(
"MotionEvent",
"Total degree of rotation is $totalDegreeOfRotation " +
"first x : "
)
totalDegreeOfRotation
}
// Rotate the ViewGroup
rotation += rotatedDegree
}
}
}
}
true
}