视图在旋转后无法在 x 轴和 y 轴上正确移动



我目前正在写一个代码在一个viewgroup,有一个框架布局的孩子,有一个视图里面。这个viewgroup负责旋转,缩放,通过在它里面实现MotionEvent来移动视图。到目前为止,我已经能够实现旋转,缩放,在它里面移动,它们工作得很好,直到我旋转整个视图组,之后,它似乎不像预期的那样移动(缩放是ok的btw)。

我猜问题是,在我旋转视图180度(甚至一点点)后,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 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
}

最新更新