观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

版权声明

本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17267995.html

本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。

前言

  此篇博客讲解View的触控事件

Touch的两种实现与关系

第一种

直接在View上实现setOnTouchListener

mBinding.multiTouchView.setOnTouchListener(object : View.OnTouchListener {
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        Log.e("zh", "setOnTouchListener:")
        return false
    }
})

第二种

在继承View的自定义View里,重写onTouchEvent方法

class MultiTouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return super.onTouchEvent(event)
    }
}

这两种实现的方式的使用上没有什么区别,都可以用一样的方式去处理触摸事件。但是,他们的先后关系我们需要搞清楚。

直接在View上实现setOnTouchListener的是大于重写onTouchEvent方法。 当setOnTouchListener返回是false的时候,这2个方法都会触发。但是,如果setOnTouchListener方法返回true(返回true就是要消费这次触控事件),就会只触发setOnTouchListener不触发重写的onTouchEvent。我们可以在View的源码中看到部分逻辑判断。

MotionEvent详解

单点触控基本概念

请注意,当你要消费事件的时候是必须返回true的,否则会出现后续其他action不返回的问题。

另外注意!这里可以使用event.getAction,但是只是在单点触控一些情况下可以使用它。一般情况下你最好使用getActionMasked方法,因为它是兼容全部的Action的。这点你可以看任意一个ACTION的注解,比如ACTION_DOWN,它们都是推荐使用getActionMasked

class MultiTouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        //请注意这里使用的是event.actionMasked
        when(event.actionMasked){
            MotionEvent.ACTION_DOWN-> {
                Log.e("zh", "按下")
                //按下必须返回true,表明需要消费此次触摸事件,这样后续的Action(例如ACTION_UP与ACTION_MOVE)才会继续返回
                return true
            }
            MotionEvent.ACTION_UP-> {
                Log.e("zh", "松开")
                return true
            }
            MotionEvent.ACTION_MOVE-> {
                Log.e("zh", "移动")
                return true
            }
            MotionEvent.ACTION_CANCEL-> {
                Log.e("zh", "取消")
                return false
            }
        }
        return super.onTouchEvent(event)
    }
}

多点触控基本概念

首先先说明一个概念,Android的多点触控有着主要指针与非主要指针的概念。首先按下的手指就是主要指针,后续按下的手指就是非主要指针。也就说你在监听处理其他手指的指针数据时,依然要对第一个按下的指针数据做监听处理。

class MultiTouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        //请注意!非主要指针触控的是使用event.actionMasked,另外它兼容主要指针。
        when(event.actionMasked){
            MotionEvent.ACTION_DOWN-> {
                Log.e("zh", "主要手指按下")
                return true
            }
            MotionEvent.ACTION_UP-> {
                Log.e("zh", "主要手指松开")
                return true
            }
            MotionEvent.ACTION_CANCEL-> {
                Log.e("zh", "主要手指取消")
                return false
            }
            MotionEvent.ACTION_POINTER_DOWN->{
                Log.e("zh", "非主要手指按下 ${event.actionIndex}")
                Log.e("zh", "非主要手指按下 id ${event.getPointerId(event.actionIndex)}")
                return true
            }
            MotionEvent.ACTION_POINTER_UP->{
                Log.e("zh", "非主要手指松开 ${event.actionIndex}")
                Log.e("zh", "非主要手指松开 id ${event.getPointerId(event.actionIndex)}")
                return true
            }
        }
        return super.onTouchEvent(event)
    }
}

getPointerId方法的解释,因为actionIndex获取的数值是自增自减整数,是时刻跟着按下的指针数量变化的。但是如果我们需要让某个指针从按下到松开都希望有一个不会变化的标识就需要使用getPointerId方法。

注意!event.actionMasked event.actionIndex 在多指的情况下使用的。因为对应手指的Action数值是使用了位运算方式,将对应Action动作与指针index都用or(|)或运算符保存在8位数里。 这里可以不要担心如何通过and(&)与运算符获得数值。因为event.actionMasked 与 event.actionIndex 这两个方法已经通过了and(&)与运算符帮你拿到了数值。源码如下:(如果你要仔细了解一下可以看看ACTION_MASK 与ACTION_POINTER_INDEX_MASK的注释)

/**
 * Return the masked action being performed, without pointer index information.
 * Use {@link #getActionIndex} to return the index associated with pointer actions.
 * @return The action, such as {@link #ACTION_DOWN} or {@link #ACTION_POINTER_DOWN}.
 */
public final int getActionMasked() {
    return nativeGetAction(mNativePtr) & ACTION_MASK;
}

/**
 * For {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP}
 * as returned by {@link #getActionMasked}, this returns the associated
 * pointer index.
 * The index may be used with {@link #getPointerId(int)},
 * {@link #getX(int)}, {@link #getY(int)}, {@link #getPressure(int)},
 * and {@link #getSize(int)} to get information about the pointer that has
 * gone down or up.
 * @return The index associated with the action.
 */
public final int getActionIndex() {
    return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
            >> ACTION_POINTER_INDEX_SHIFT;
}

多点移动触控基本概念

多点触控的移动,并没有对应某个指针移动了的概念。通俗的讲就是如果你按下了3个指针位置,如果其中一个移动了位置,系统认为这3个都触发了移动。所以,你取值需要将三个指针位置变化都取出来。

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Point
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View

class MultiTouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private val mPointList = mutableListOf<Pair<Int, Point>>()

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when(event.actionMasked){
            MotionEvent.ACTION_DOWN-> {
                Log.e("zh", "主要手指按下")
                return true
            }
            MotionEvent.ACTION_UP-> {
                Log.e("zh", "主要手指松开")
                return true
            }
            MotionEvent.ACTION_CANCEL-> {
                Log.e("zh", "主要手指取消")
                return false
            }
            MotionEvent.ACTION_MOVE-> {
                mPointList.clear()
                for(index in 0 until event.pointerCount){
                    val point = Point(event.getX(index).toInt(), event.getY(index).toInt())
                    mPointList.add(index to point)
                }
                Log.e("zh", "移动后每一个坐标点的位置 ${mPointList}")
                return true
            }
            MotionEvent.ACTION_POINTER_DOWN->{
                Log.e("zh", "非主要手指按下 ${event.actionIndex}")
                return true
            }
            MotionEvent.ACTION_POINTER_UP->{
                Log.e("zh", "非主要手指松开 ${event.actionIndex}")
                return true
            }
        }
        return super.onTouchEvent(event)
    }
}

MotionEvent的Action

/**
 * 操作代码中作为操作本身的部分的位掩码。参考getActionMasked()与getActionIndex()方法的代码
 */
public static final int ACTION_MASK             = 0xff;

/**
 * 按下的手势已经开始,动作包含初始开始位置。
 */
public static final int ACTION_DOWN             = 0;

/**
 * 一个按下的手势已经完成,动作包含最终释放位置以及自上次向下或移动事件以来的任何中间点。
 */
public static final int ACTION_UP               = 1;

/**
 * 在按下手势期间发生了移动变化,变化在ACTION_DOWN和ACTION_UP之间。该运动包含最近的点,以及自上次下跌或移动事件以来的任何中间点。
 */
public static final int ACTION_MOVE             = 2;

/**
 * 当前手势已中止。
 */
public static final int ACTION_CANCEL           = 3;

/**
 * 触控到当前视图层次结构的边界之外,在UI元素的正常边界之外发生了移动。这并没有提供一个完整的手势,而只是提供了movementtouch的初始位置。
 */
public static final int ACTION_OUTSIDE          = 4;

/**
 * 一个非主指针已按下
 */
public static final int ACTION_POINTER_DOWN     = 5;

/**
 * 一个非主指针已松开。
 */
public static final int ACTION_POINTER_UP       = 6;

/**
 * 鼠标悬停事件,这个只出现在插入鼠标的情况下
 */
public static final int ACTION_HOVER_MOVE       = 7;

/**
 * 运动事件包含相对的垂直和或水平滚动偏移量。
 * 需要配件使用getAxisValue(int) 从 AXIS_VSCROLL和 AXIS_HSCROLL 获得滚动信息能down,也可能不会down。
 * 此常量需要结合onGenericMotionEvent使用
 */
public static final int ACTION_SCROLL           = 8;

/**
 * 鼠标悬停进入
 */
public static final int ACTION_HOVER_ENTER      = 9;

/**
 * 动作代码中表示指针索引的位,与{@link ACTION_POINTER_DOWN}和{@link ACTION_POINTER_UP}一起使用。
 * 通过{@link ACTION_POINTER_INDEX_SHIFT}向下移动提供了实际的指针索引,其中可以找到指针向上或向下移动的数据;
 * 你可以用{@link getPointerId(int)}获取它的标识符,用{@link getX(int)}获取实际数据,等等。
 */
public static final int ACTION_POINTER_INDEX_MASK  = 0xff00;

/**
 * 保存指针索引的动作位的位移位,由{@link ACTION_POINTER_INDEX_MASK}定义。
 * @see #getActionIndex
 */
public static final int ACTION_POINTER_INDEX_SHIFT = 8;

MotionEvent的API方法

 

End

posted on 2023-03-29 13:51  观心静  阅读(237)  评论(0编辑  收藏  举报