zoukankan      html  css  js  c++  java
  • 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作为结束。

  • 相关阅读:
    定义全局时间过滤器
    vue局部过滤器和全局过滤器
    vue-ref指令
    vue进行代码排序
    vue-通过name进行数据过滤
    将vue文档下载到本地预览
    由于vue的for循环id并不严谨,提高id严谨性
    vue指令v-for报错:Elements in iteration expect to have 'v-bind:key' directives.eslint-plugin-vue
    FlowPortal BPM历史版本升级说明
    102从 Outlook 中将电子邮件、联系人和日历导出到 .pst 文件
  • 原文地址:https://www.cnblogs.com/pravez/p/12558200.html
Copyright © 2011-2022 走看看