调用 canvas.scale() 不会缩放视图的点击侦听器区域

Calling canvas.scale() doesn't scale the click listener area of the views

我在 LinearLayout 中有一堆按钮。在 LinearLayout 中,我重写 onDraw() 以调用 canvas.scale(scale, scale),其中 scale 是某个数字。 LinearLayout 中的按钮会在视觉上缩放,但点击区域不会随着视觉表示移动和缩放。也就是说,点击区域在缩放后注册到原来的位置

这是一个使用滑块缩放 canvas 的演示。缩放后可以看到按钮只有在原位置点击时才会显示点击动画

layout.xml

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

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.google.android.material.slider.Slider
            android:id="@+id/mySlider"
            android:valueFrom=".5"
            android:valueTo="3"
            android:value="1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <com.dgnt.playground.MyLinearLayout
            android:id="@+id/myContainer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="My Testing String Is A long one My Testing String Is A long one" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="My Testing String Is A long one My Testing String Is A long one" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="My Testing String Is A long one My Testing String Is A long one" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="My Testing String Is A long one My Testing String Is A long one" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="My Testing String Is A long one My Testing String Is A long one" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="My Testing String Is A long one My Testing String Is A long one" />



        </com.dgnt.playground.MyLinearLayout>

    </LinearLayout>
</layout>

自定义线性布局

class MyLinearLayout : LinearLayout {
    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    )

    init {
        setWillNotDraw(false)
    }

    private var scale: Float = 1.0f;

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.scale(scale, scale)
    }

    fun setScale(value: Float) {
        scale = value
        invalidate()
        requestLayout()
    }

}

主要Activity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityMainBinding.inflate(layoutInflater).apply {
            setContentView(root)

            mySlider.addOnChangeListener { slider, value, fromUser ->

                myContainer.setScale(value)
            }
        }
    }
}

我尝试覆盖 dispatchTouchEvent 来确定用户的触摸坐标是否拦截了视图,但似乎 view.getLocationOnScreen() 也没有考虑 canvas 的比例正如这个问题所证明的 Android: Get View Location on Screen after View is scaled?

我该怎么做才能让 click/touch 准确记录视图在缩放 canvas 之前和之后的位置?

解决此问题的一种方法是缩放您的触摸事件,我在我创建的具有缩放和平移的自定义视图中完成了此操作。

在你的 MyLinearLayout 覆盖 dispatchTouchEvent

创建一个应用比例因子的 Matrix,然后在调用原始 dispatchTouchEvent

之前用比例矩阵转换 MotionEvent

抱歉 Java 但类似的东西(有点来自记忆,而不是我是如何做到的,因为它有一些更复杂的平移用例和屏幕不同部分的不同平移)


@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

   Matrix matrix = new Matrix();
   Matrix mappingMatrix = new Matrix();

   matrix.setScale(scaleFactor, scaleFactor);
   // From memory the Matrix for the transform has to be inverted
   matrix.invert(mappingMatrix);

   // Copy motion event because I seem to remember you cannot change the original motion event
   MotionEvent transformEvent = MotionEvent.obtain(ev);
   transformEvent.transform(mappingMatrix);

   // call parent class so this processes the modified event as normal
   boolean returnVal = super.dispatchTouchEvent(transformEvent);

   // Tidy up the copied motion event to not leak memory
   transformEvent.recycle();

   return returnVal;

}