Android 触摸事件的传递过程

Android的触摸事件回调函数主要有三个 dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。而传递触摸事件的主体也有三种,从父层到子层分别为Activity、ViewGroup和View。接下来先分别讲解这几个回调函数的作用,以及整个触摸事件的传递过程。

回调函数 运行次序 位于Activity 位于ViewGroup 位于View 作用 触发条件 返回true的含义 返回false的含义
dispatchTouchEvent() 1 传递触摸事件 按下点击/父层dispatchTouchEvent()=true且onInterceptTouchEvent()=false 可以接收并传递此事件 不接收此事件,不会往下传递
onInterceptTouchEvent() 2 × × 拦截触摸事件 本层dispatchTouchEvent()=true 不再往下再传递,从这开始试图消费 不拦截,继续向子层传递
onTouchEvent() 3 消费触摸事件 本层dispatchTouchEvent()=true且所有子层的onTouchEvent()=false 消费此事件,不再向父层请求消费 不消费,让父层去消费

准备工作


import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

public class CustomLinearLayout extends LinearLayout {
    private static String TAG = "20200324-布局";
    public CustomLinearLayout(Context context) {
        super(context);
    }

    public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG+" "+getTag(), "dispatchTouchEvent: "+MotionEvent.actionToString(ev.getAction()));

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG+" "+getTag(), "onInterceptTouchEvent: "+MotionEvent.actionToString(ev.getAction()));

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.d(TAG+" "+getTag(), "onTouchEvent: "+MotionEvent.actionToString(ev.getAction()));

        return super.onTouchEvent(ev);
    }
}
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;

public class CustomButton extends androidx.appcompat.widget.AppCompatButton {
    private static final String TAG = "20200324-按钮";
    public CustomButton(Context context) {
        super(context);
    }

    public CustomButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "dispatchTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onTouchEvent: "+MotionEvent.actionToString(ev.getAction()));
        return super.onTouchEvent(ev);
    }
}

布局(全路径类名的包路径省略)

<CustomLinearLayout android:tag="外层">
    <CustomLinearLayout android:tag="内层">
        <Button />
    </LinearLayout>
</LinearLayout>

使用时,利用getTag().toString拿到在布局中定义的tag,来确定哪个是外层的ViewGroup哪个是内层的ViewGroup。

事件的传递过程

默认不做任何修改的情况下

dispatchTouchEvent() = true
onInterceptTouchEvent() = false
ViewGroup.onTouchEvent() = false; Button.onTouchEvent() = true

点击这个Button,暂时只考虑按下,不移动和抬起,触摸事件的传递和消费过程为:

20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局?内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?内层: onInterceptTouchEvent: ACTION_DOWN
20200324-按钮: dispatchTouchEvent: ACTION_DOWN
20200324-按钮: onTouchEvent: ACTION_DOWN

可以看到事件依次从外层传递到最内的Button,然后Button.onTouchEvent()默认为true,消费了此事件。

Button不消费的情形

可以想象,Button不消费,父层的ViewGroup默认也不消费,则最后触摸事件会以U形传回Activity。
测试其消费过程为:

20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局?内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?内层: onInterceptTouchEvent: ACTION_DOWN
20200324-按钮: dispatchTouchEvent: ACTION_DOWN
20200324-按钮: onTouchEvent: ACTION_DOWN
20200324-布局?内层: onTouchEvent: ACTION_DOWN
20200324-布局?外层: onTouchEvent: ACTION_DOWN
20200324-Activity: onTouchEvent: ACTION_DOWN

中间某一层dispatchTouchEvent()设置为false 的情形

dispatchTouchEvent()为false表示我不用,子层也别用,传递到此为止。将上述例子中的内层的dispatchTouchEvent()设置为false,onTouchEvent()设置为true(验证其和dispatchTouchEvent()=false的区别)测试其消费过程为:

20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局?内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: onTouchEvent: ACTION_DOWN
20200324-Activity: onTouchEvent: ACTION_DOWN

可以看到虽然内层的onTouchEvent设置为true表示内层能消费,但是dispatch为false了,消费不到只能传回去,事件最后被Activity自己拿回去了,因为内层dispatchTouchEvent()=false自己不要,外层的onTouchEvent()默认为false也不消费,只能拿给Activity。

中间某一层onInterceptTouchEvent()设置为true 的情形

onInterceptTouchEvent()为true表示子层别用了,我先拿着,具体用不用由onTouchEvent()决定。将上述例子中的内层的onInterceptTouchEvent()设置为true,onTouchEvent()设置为true(验证其和dispatchTouchEvent()=false的区别),测试其消费过程为:

20200324-Activity: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?外层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局?内层: dispatchTouchEvent: ACTION_DOWN
20200324-布局?内层: onInterceptTouchEvent: ACTION_DOWN
20200324-布局?内层: onTouchEvent: ACTION_DOWN

可以看到事件被内层消费了(与上一种情况进行比较可以发现)

后续事件

初始事件(一般为ACTION_DOWN按下)被某一层消费以后,本次触摸事件内的此后的事件的传递就不再是U形了。
比如上述的例子中,如果ViewGroupB来消费此事件,第一次的ACTION_DOWN的传递流程为:ViewGroupA -> ViewGroupB -> Button -> ViewGroupB消费,那么下一次传递过来ACTION_MOVE和ACTION_UP时,不会再从Button绕一遍,只是从ViewGroupA -> ViewGroupB 。
如果此时,在传递ACTION_MOVE和ACTION_UP时,由ViewGroupA拦截掉了(他不拦ACTION_DOWN,就拦别的),无法到达ViewGroupB,则会给ViewGroupB传递一个ACTION_CANCEL作为结束。

Android 触摸事件的传递过程

上一篇:Android Flutter AndroidX incompatibilities报错处理


下一篇:Window10搭建Flutter for Android环境