Sub View setting LayoutParams does not slide smoothly and flickers

Sub View setting LayoutParams does not slide smoothly and flickers

Ado!

Currently, I am going to implement a function by myself. There are 4 TextViews stacked together in a FrameLayout. I want to monitor touch events and slide the top TextView away in turn.

The layout is as follows

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.original.touchevent.MoveView
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@android:color/holo_blue_dark">

        <com.original.touchevent.MoveChildView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:text="1"
            android:textSize="30sp"
            android:gravity="center"
            android:textColor="@color/white"/>

        <com.original.touchevent.MoveChildView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:text="2"
            android:textSize="30sp"
            android:gravity="center"
            android:textColor="@color/white"/>

        <com.original.touchevent.MoveChildView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:text="3"
            android:textSize="30sp"
            android:gravity="center"
            android:textColor="@color/white"/>

        <com.original.touchevent.MoveChildView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:text="4"
            android:textSize="30sp"
            android:gravity="center"
            android:background="@android:color/holo_red_dark"
            android:textColor="@color/white"/>

    </com.original.touchevent.MoveView>

</FrameLayout>

Where com.original.touchevent.MoveView is a custom View, inherited from FrameLayout

com.original.touchevent.MoveChildView is also a custom View, inherited from TextView

Wrong implementation

First of all, my first thought was to rewrite the onTouchEvent method in the custom TextView , and then change its position according to changing its LayoutParams , but after the final implementation, I found that the dragging was not smooth and would flash back and forth. The code implementation is as follows:

//集成自TextView的子View
class MoveChildView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    AppCompatTextView(context, attrs, defStyleAttr) {

    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
    constructor(context: Context) : this(context, null, 0)

    private var lastX = 0
    private var lastY = 0

    override fun onTouchEvent(event: MotionEvent?): Boolean {

        event?.let {
            val x = it.x
            val y = it.y
            val action = it.action
            when (action) {
                MotionEvent.ACTION_DOWN -> {
                    lastX = x.toInt()
                    lastY = y.toInt()
                }
                MotionEvent.ACTION_MOVE -> {
                    val dx = x.toInt() - lastX
                    val dy = y.toInt() - lastY
                    move(dx, dy)
                    lastX = x.toInt()
                    lastY = y.toInt()
                }
            }
        }

        return true
    }

    fun move(x: Int, y: Int) {
        val params = layoutParams as FrameLayout.LayoutParams
        params.leftMargin += x
        params.topMargin += y
        setLayoutParams(params)
    }
}

Why does this happen?

To analyze this problem, you must first understand the definitions of the following two methods provided by MotionEvent:

//以触摸的View的左上角为基准,当前点击位置的X坐标
public float getX()
//以触摸的View的左上角为基准,当前点击位置的Y坐标
public float getY()

Please remember that the coordinates are based on the upper left corner of the touch View. If the logic of this movement is written in the sub-View, then your View moves, then the position of the upper left corner of the View changes, and then the basis for calculating the coordinates changes, then the generated x and y coordinate values ​​of the finger touch are actually If calculated based on the constantly changing coordinates of the uppermost corner, it will be inaccurate, and even dx and dy will generate values ​​in the opposite direction of sliding. For example, when sliding to the right, dx is reflected as a negative number.

correctly implemented

Because the child View is to be moved, the coordinates of the upper left corner of the external parent View are actually unchanged. Only with a constant and correct reference can the calculated dx and dy sliding distances be accurate. Therefore, the correct implementation is to write the sliding logic in the parent View. Of course, you can also use the getRawX and getRawY methods provided by MotionEvent to calculate the coordinates, because the reference of these two methods is the upper left corner of the screen. The specific code is as follows:

//继承自FrameLayout的父View
class MoveView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    FrameLayout(context, attrs, defStyleAttr) {

    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
    constructor(context: Context) : this(context, null, 0)

    private var lastX = 0
    private var lastY = 0
    private var moveView: View? = null

    override fun onTouchEvent(event: MotionEvent?): Boolean {

        event?.let {
            val x = it.x
            val y = it.y
            val action = it.action
            when (action) {
                MotionEvent.ACTION_DOWN -> {
                    lastX = x.toInt()
                    lastY = y.toInt()
                    moveView = findMoveView(x, y)
                }
                MotionEvent.ACTION_MOVE -> {
                    val dx = x.toInt() - lastX
                    val dy = y.toInt() - lastY
                    moveView?.let {
                        move(it, dx, dy)
                    }
                    lastX = x.toInt()
                    lastY = y.toInt()
                }
            }
        }

        return true
    }

    fun inView(view: View, x: Float, y: Float): Boolean{
        return x >= view.left && x <= view.right && y >= view.top && y <= view.bottom
    }

    fun findMoveView(x: Float, y: Float): View?{
        for(index in childCount-1 downTo 0){
            if(inView(getChildAt(index), x, y)){
                return getChildAt(index)
            }
        }
        return null
    }

    fun move(view: View, x: Int, y: Int) {
        val params = view.layoutParams as FrameLayout.LayoutParams
        params.leftMargin += x
        params.topMargin += top + y
        view.layoutParams = params
    }
}

So far, this problem is over.

Guess you like

Origin blog.csdn.net/sinat_34388320/article/details/125784624