android 事件传递机制

作者: 梦语少年 | 来源:发表于2017-06-14 17:56 被阅读137次

    一.简述

    android上所有的事件操作都是基于用户对屏幕的触摸与滑动进行分解,进而对用户不同的操作进行监听;如:点击事件、双击事件、长按事件等等。

    一次完整的事件传递主要包含三个阶段,分别是事件的分发(Dispatch)、拦截(Intercept)、消费(Consume)。

    关键词

    • ACTION_DOWN:用户手指按下屏幕:
    • ACTIOJN_MOVE:用户手指在屏幕上滑动,滑动的距离大于阀值,即产生滑动事件;
    • ACTiON_UP:用户手指离开屏幕:
    • 分发(Dispatch):事件分发,即dispatchTouchEvent();在android中所有的触摸事件都有由dispatchTouchEvent()进行分发处理的。返回true事件被消费,返回super.dispatchTouchEvent(),会触发拦截机制;
    • 拦截(Intercept): 事件拦截,即onInterceptTouchEvent();该方法,只在ViewGroup及其子类中存在,view和Activity 中不存在。实现逻辑,返回true,被拦截,返回false或super.onInterceptTouchEvent(),向下传递;
    • 消费(Consume):事件消费,即onTouchEvent();返回true,事件被消费掉(处理),不会向上传递给父视图。返回false,事件不会被处理,向上传递给父视图,进行事件分发;

    视图与事件的对应

    视图 分发 拦截 消费
    Activity dispatchTouchEvent -- onTouchEvent
    ViewGroup dispatchTouchEvent onInterceptTouchEvent onTouchEvent
    View dispatchTouchEvent -- onTouchEvent

    二.View的事件传递

    代码

    • Activity:EventActivity
    
    public class EventActivity extends AppCompatActivity {
        private static final String TAG = EventActivity.class.getSimpleName();
        EventTextView etvEvent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_event);
            etvEvent = (EventTextView) findViewById(R.id.etv_event);
            etvEvent.setOnClickListener(onClickListener);
            etvEvent.setOnTouchListener(onTouchListener);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "dispatchTouchEvent ACTION_UP");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
                    break;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "onTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "onTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "onTouchEvent ACTION_UP");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e(TAG, "onTouchEvent ACTION_CANCEL");
                    break;
            }
            return super.onTouchEvent(event);
        }
    
        private View.OnClickListener onClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "onClickListener ONCLICK");
            }
        };
        private View.OnTouchListener onTouchListener = new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TAG, "onTouchListener ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e(TAG, "onTouchListener ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e(TAG, "onTouchListener ACTION_UP");
                        break;
                    case MotionEvent.ACTION_CANCEL:
                        Log.e(TAG, "onTouchListener ACTION_CANCEL");
                        break;
                }
                return false;
            }
        };
    } 
    
    
    • 自定义View:EventTextView
    
    public class EventTextView extends AppCompatTextView {
        private static final String TAG = EventTextView.class.getSimpleName();
    
        public EventTextView(Context context) {
            super(context);
        }
    
        public EventTextView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public EventTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "dispatchTouchEvent ACTION_UP");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
                    break;
            }
            return super.dispatchTouchEvent(event);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "onTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "onTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "onTouchEvent ACTION_UP");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e(TAG, "onTouchEvent ACTION_CANCEL");
                    break;
            }
            return super.onTouchEvent(event);
        }
    }
    
    • 布局文件:activity_event.xml
    <?xml version="1.0" encoding="utf-8"?> 
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
            xmlns:tools="http://schemas.android.com/tools" 
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context="study.zxh.com.androidstudydemp.event.EventActivity">       
             <study.zxh.com.androidstudydemp.event.EventTextView 
                      android:id="@+id/etv_event"
                      android:layout_width="match_parent" 
                      android:layout_height="50dp" android:gravity="center_vertical" 
                      android:text="android事件传递机制测试--view"   
                      android:textSize="16dp" /> 
    </LinearLayout>
    

    代码概述

    • 相关代码优先级:
      Activity > ViewGroup > View
      ACTION_DOWN > ACTION_MOVE > ACTION_UP
      dispatchTouchEvent > OnTouchListener

    • 代码结构很简单,分别对Activity和View的dispatchTouchEvent以及onTouchEvent进行的日志记录;同时,对常用的事件OnClickListener以及OnTouchListener进行了记录,对于得到的结果,可以划分为以下几种:

    1.事件被activity 的dispatchTouchEvent消费:
    dispatchTouchEvent消费事件

    结论

    • 事件消费后,不会再向下传递,也不会触发activity的onTouchEvent();
    2.事件被View 的OnTouchListener消费:
    事件被View 的OnTouchListener消费
    结论
    • 事件消费后,不会触发activty的onTouchEvent()方法;
    • 事件消费后,会触发view绑定的OnTouchListener监听;
    • 事件的传递,每次都是由Activity的dispatchTouchEvent开始的;
    • 事件的传递,OnTouchListener的优先级高于onTouchEvent的优先级;
    • 事件的监听依赖与onTouchEvent方法;
    3.事件被View的dispatchTouchEvent消费:
    事件被View的dispatchTouchEvent消费
    结论
    • 事件被消费,不会触发任何的onTouchEvent()方法;
    • 事件被消费,不会触发任何的监听事件;
    4.事件被View的onTouchEvent消费:
    事件被View的onTouchEvent消费
    结论
    • 所有事件都会单向传递到View的onTouchEvent方法,但是不会再向上传递;
    • 事件传递的顺序是:
      dispatchTouchEvent(Activity)-->dispatchTouchEvent(View)-->onTouchListener(View)-->onTouchEvent(View)
    • 没有调用到onClickListener。
    5.事件被Activity的onClickListener消费:
    事件被Activity的onClickListener消费
    结论
    • onClick事件发生在View的onTouchEvent方法之后,消费掉就不会再向上传递;
    • 事件传递的顺序是:
      dispatchTouchEvent(Activity)-->dispatchTouchEvent(View)-->onTouchListener(View)-->onTouchEvent(View)-->onClickListener
    6.事件被Activity的onTouchEvent消费:
    事件被Activity的onTouchEvent消费
    结论
    • 会触发Activity以及View的所有相关方法;
    • 事件触发的流程是以Activity的dispatchTouchEvent开始,以activity的onTouchEvent结束

    二.View的事件传递

    代码

    • 自定义ViewGroup:EventLinearLayout
    public class EventLinearLayout extends LinearLayout {
        private static final String TAG = "EventLinearLayout";
    
        public EventLinearLayout(Context context) {
            super(context);
        }
    
        public EventLinearLayout(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public EventLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "dispatchTouchEvent ACTION_UP");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
                    break;
            }
            return super.dispatchTouchEvent(event);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "onTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "onTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "onTouchEvent ACTION_UP");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e(TAG, "onTouchEvent ACTION_CANCEL");
                    break;
            }
            return super.onTouchEvent(event);
        }
    }
    
    • 修改布局文件:activity_event.xml
    <?xml version="1.0" encoding="utf-8"?>
    <study.zxh.com.androidstudydemp.event.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
    
        <study.zxh.com.androidstudydemp.event.EventTextView
            android:id="@+id/etv_event"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            android:text="android事件传递机制测试--view"
            android:textSize="16dp" />
    </study.zxh.com.androidstudydemp.event.EventLinearLayout>
    
    
    1.事件被ViewGroup的dispatchTouchEvent 消费
    事件被ViewGroup的dispatchTouchEvent 消费
    结论
    • 事件被ViewGroup的dispatchTouchEvent消费,没有触发任何onTouchEvent();
    2.ViewGroup的dispatchTouchEvent 返回false
    ViewGroup的dispatchTouchEvent 返回false
    结论
    • 事件没有被ViewGroup消费;
    • 事件不会向下传递;
    • 没有调用ViewGroup的interceptTouchEvent(拦截器);
    • 事件回传到activity的onTouchEvent后事件结束;
    3.ViewGroup的dispatchTouchEvent 返回super.dispatchTouchEvent()
    ViewGroup的dispatchTouchEvent 返回super.dispatchTouchEvent()
    结论
    • 事件会正常向下传递,若没有被消费,则会回传给activity的onTouchEvent处理;
    4. 事件被ViewGroup的interceptTouchEvent 拦截
    事件被ViewGroup的interceptTouchEvent 消费
    结论
    • 事件被拦截之后,会直接调用ViewGroup的onTouchEvent进行处理;
    • 事件被拦截,就不会再向下传递。
    5. ViewGroup的interceptTouchEvent 返回 false
    ViewGroup的interceptTouchEvent 返回 false
    结论
    • 当返回false时,事件正常向下传递,不会有任何影响;
    6. ViewGroup的interceptTouchEvent 返回 super.onInterceptTouchEvent()
    ViewGroup的interceptTouchEvent 返回 super.onInterceptTouchEvent()
    结论
    • 返回super.onInterceptTouchEvent(),显示的结结果,与返回false相同;
      问题:若是多层ViewGroup嵌套,又是什么结果呢?
    7. 事件被ViewGroup的onTouchEvent 消费
    事件被ViewGroup的onTouchEvent 消费
    结论
    • 事件会先触发View的onTouchEvent,然后再出发ViewGroup的TouchEvent;
    8. ViewGroup的onTouchEvent 返回false或super.onTouchEvent()
    ViewGroup的onTouchEvent 返回false
    ViewGroup的onTouchEvent 返回super.onTouchEvent()
    结论
    • 两种情况效果相同

    结论

    • 事件被ViewGroup拦截,没有触发任何onTouchEvent();

    三. 分析返回false与super的区别

    (在ViewGroup嵌套ViewGroup情况下)

    代码

    • 修改布局文件:activity_event.xml
    <?xml version="1.0" encoding="utf-8"?>
    <study.zxh.com.androidstudydemp.event.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
    
        <study.zxh.com.androidstudydemp.event.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
    
            <study.zxh.com.androidstudydemp.event.EventTextView
                android:id="@+id/etv_event"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:gravity="center_vertical"
                android:text="android事件传递机制测试--view"
                android:textSize="16dp" />
        </study.zxh.com.androidstudydemp.event.EventLinearLayout>
    </study.zxh.com.androidstudydemp.event.EventLinearLayout>
    

    1.dispatchTouchEvent

    activty的disptchTouchEvent返回false

    activty的disptchTouchEvent返回false

    activty的disptchTouchEvent返回super

    activty的disptchTouchEvent返回super

    ViewGroup的disptchTouchEvent返回false

    ViewGroup的disptchTouchEvent返回false

    ViewGroup的disptchTouchEvent返回super

    ViewGroup的disptchTouchEvent返回super

    View的disptchTouchEvent返回false

    View的disptchTouchEvent返回false

    View的disptchTouchEvent返回super

    View的disptchTouchEvent返回super

    2.interceptTouchEvent

    interceptTouchEvent返回false

    interceptTouchEvent返回false

    interceptTouchEvent返回super

    interceptTouchEvent返回super

    3.onTouchEvent

    onTouchEvent返回super

    onTouchEvent返回super

    onTouchEvent返回false

    onTouchEvent返回false

    总结

        1.dispatchTouchEvent返回false,事件不会继续向下传递,但是会向上调用,调用上一级的onTouchEvent,进行处理;
        2.dispatchTouchEvent返回super,事件会向下传递,调用相应等级的dispatchTouchEvent或interceptTouchEvnt进行继续处理;
        3.interceptTouchEvent与onTouchEvent返回false或super没有区别;
        备注:dispatchTouchEvent返回false和super,没有区别,因为它们没有需要向下传递的途径
    

    四. 事件分发机制流程

    事件分发机制流程
    详解
    1. 事件由activity的dispatchTouchEvent开始,事件被消费即结束;
    2. 事件没有被消费,若返回false,直接调用上一级的onTouchEvent进行消费;
    3. 若返回super,会执行同级别下的相关方法,即如下两种途径:
      1). 若是ViewGroup,先执行interceptTouchEvent方法,返回true即拦截,运行同级别onTouchEvent方法进行消费;返回false或super,事件向下传递;
      2). 若不是ViewGroup,事件向下传递,进入view层,再次进行事件分发,若被消费,事件结束,若没有被消费,事件会由View的onTouchEvent依次传回到Activity的onTouchEvent方法最终结束;
    完整的事件传递流程
        dispatchTouchEvent(Activity)--> dispatchTouchEvent(ViewGroup)-->  interceptTouchEvent(ViewGroup) -->dispatchTouchEvent(View)--> onTouchEvent(View) --> onTouchEvent(ViewGroup)--> onTouchEvent(Activity)

    相关文章

      网友评论

        本文标题:android 事件传递机制

        本文链接:https://www.haomeiwen.com/subject/sdhlqxtx.html