美文网首页
Android 事件分发笔记

Android 事件分发笔记

作者: 牧区叔叔 | 来源:发表于2020-10-20 22:28 被阅读0次

      小白的我第一次听到事件分发!毛线?虽然日常一直用到,但是不清楚啊,所以通过给自己提问问题,在解决问题的同时也就知道了其中的原理。

    链接:https://www.cnblogs.com/huihuizhang/p/7633552.html
    链接:https://zhuanlan.zhihu.com/p/27608989
    链接:https://www.cnblogs.com/chengxuyinli/p/9979826.html

    1. 什么是事件?
    2. 什么是事件分发?
    3. 什么是事件分发机制?
    4. 在哪里可以进行事件分发?
    5. 事件分发持有者?也就是对象是谁?
    6. 被分发的对象是谁?
    7. 事件的传递流程?

    1.什么是事件?

     当用户点通过屏幕与手机进行交互的时候所体现的动作就是事件,包括有点击(短按)、长按、滑动、抬起等等这些都叫事件。按照面向对象的思想的话这些动作被封装成了MotionEvent,也就是说事件的对象就是MotionEvent。

    2. 什么是事件分发?

     事件从屏幕传递给app视图的各个View,这个传递过程就叫是加你分发。然后其中某个View来使用这个事件或者忽略这个事件,这整个过程就是事件分发机制了。

    3. 什么是事件分发机制?

     事件从屏幕传递给app视图的各个View,然后其中某个View来使用这个事件或者忽略这个事件,这整个过程就是事件分发机制了。

    4. 在哪里可以进行事件分发(事件分发的组件/事件分发者)?

     事件是在Activity、View Group、View 这三层中实现的,三者一般结构为:


    在这里插入图片描述

    从上图中可以看出,Activity包括了ViewGroup,ViewGroup又可以包含多个View。

    组件 特点 举例
    Activity 安卓视图类 如MainActivity
    ViewGroup View的容器,可以包含若干View 各种布局类(LinearLayout...等)
    View UI类组件的基类 如按钮、文本框

    5. 事件分发持有者?也就是对象是谁?

     MotionEvent

    6. 被分发的对象是谁?

     被分发的对象是用户触摸屏幕而产生的点击事件,事件主要包括:按下、滑动、抬起与取消。这些事件被封装成MotionEvent对象。该对象中的主要事件如下表所示:

    事件 触发场景 单次事件流中触发的次数
    MotionEvent.ACTION_DOWN 在屏幕按下时 1次
    MotionEvent.ACTION_MOVE 在屏幕上滑动时 0次或多次
    MotionEvent.ACTION_UP 在屏幕抬起时 0次或1次
    MotionEvent.ACTION_CANCLE 滑动超出控件边界时 0次或1次

    按下、滑动、抬起、取消这几种事件组成了一个事件流。事件流以按下为开始,中间可能有若干次滑动,以抬起或取消作为结束。

    安卓对事件分发的处理过程中,主要是对按下事件作分发,进而找到能够处理按下事件的组件。对于事件流中后续的事件(如滑动、抬起等),则直接分发给能够处理按下事件的组件。

    7. 事件的传递流程?

    流程

    每一个Activity内部都包含一个Window用来管理要显示的视图。而Window是一个抽象类,其具体实现是 PhoneWindow类。DecovrView作为PhoneWindow的一个内部类,实际管理着具体视图的显示。他是FrameLayout的子类,盛放着我们的标题栏和根视图。我们自己写的一些列View和ViewGroup都是由他来管理的。因此事件分发的时候,顶层的这些“大View”们实际上是不会对事件有任何操作的,他们只是把事件不断的向下递交,直到我们可以使用这些事件。

    所以,事件自顶向下的传递过程应该是这样的:
    Activity(不处理)-> 根View -> 一层一层ViewGroup(如果有的话) -> 子View

    如果传递到最后我们的子View们没有处理这一事件怎么办呢?这时候就会原路返回,最终传递给Activity。只有当Activity也没有处理这一事件时,这一事件才会被丢弃。

    Activity(不处理则丢弃) <- 根View <- 一层一层ViewGroup(如果有的话) <- 子View

    具体在传递事件的时候,是由以下三个方法来控制的:

    • dispatchTouchEvent : 分发事件
    • onInterceptTouchEvent : 拦截事件
    • onTouchEvent : 消费事件

    它们并不存在于所有负责分发的组件中,其具体情况总结于下面的表格中:

    组件 dispatchTouchEvent onTouchEvent onInterceptTouchEvent
    Activity ×
    ViewGroup
    View ×

    从表格中看,dispatchTouchEvent,onTouchEvent方法存在于上文的三个组件中。而onInterceptTouchEvent为ViewGroup独有。这些方法的具体作用在下文作介绍。

    看代码了解下以上三个方法!

    准备工作:

    • 创建Activity重写dispatchTouchEvent,onTouchEvent方法
    • 自定义一个ViewGroup重写dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent 方法
    • 自定义一个Veiw重写dispatchTouchEvent,onTouchEvent方法

    效果

    在这里插入图片描述

    这是我的代码

    
    // 1. XML代码
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:background="@color/colorPrimary"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity2">
    
        <com.example.mybase_one.MyViewGroup
            android:background="@color/colorAccent"
            android:layout_width="300dp"
            android:layout_height="300dp">
    
           <com.example.mybase_one.Mybutton
               android:id="@+id/bt"
               android:gravity="center"
               android:background="@color/colorPrimary"
               android:text="按钮"
               android:layout_width="50dp"
               android:layout_height="50dp"/>
        </com.example.mybase_one.MyViewGroup>
        </LinearLayout>
    
    // 2. Activity代码
    package com.example.mybase_one;
    
    import android.os.Bundle;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    public class MainActivity2 extends AppCompatActivity {
        private Mybutton bt;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
            initView();
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            Log.i("sj", "onTouchEvent: ----"+"activity---消费事件");
            return super.onTouchEvent(event);
    //        return true;
    //        return false;
        }
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            Log.i("sj", "dispatchTouchEvent: ----"+"activity---的事件分发 【return super 往下传 】 【return true 消费】【return false 消费】");
            return super.dispatchTouchEvent(ev);
    //        return true;
    //        return false;
        }
        private void initView() {
            bt = (Mybutton) findViewById(R.id.bt);
            bt.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.i("sj", "onClick: ----"+"此事件View通过onClick已经消费");
                }
            });
            bt.setOnTouchListener(new View.OnTouchListener() {
                /**
                 *  action 只有3中状态!
                 *  action = 0; 表示按下 MotionEvent.ACTION_DOWN
                 *  action = 2; 表示移动 MotionEvent.ACTION_MOVE
                 *  action = 1; 表示抬起 MotionEvent.ACTION_UP
                 */
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                   if (event.getAction()==0){
                       Log.i("sj", "按钮执行--按压动作---ACTION_DOWN ");
                   }else if (event.getAction()==1){
                       Log.i("sj", "按钮执行了--抬起动作---ACTION_UP  ");
                   }else {
                       Log.i("sj", "按钮执行了--滑动动作---ACTION_MOVE ");
                   }
                    return false;
    //               return true;
                }
            });
        }
    }
    
    // 3. 自定义ViewGroup代码
    package com.example.mybase_one;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewGroup;
    
    public class MyViewGroup extends ViewGroup {
        public MyViewGroup(Context context) {
            super(context);
        }
    
        public MyViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @SuppressLint("NewApi")
        public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int childCount = getChildCount();
            int layoutWidth = r - l;
            int left = 0;
            int right = 0;
            int top = 0;
            for (int i = 0; i < childCount; i++) {
                View view = getChildAt(i);
                // 换行:比较right,right如果大于Layout宽度,那么要换行
                right = left + view.getMeasuredWidth();
                if (right > layoutWidth) {
                    left = 0;
                    right = left + view.getMeasuredWidth();
                    top += view.getMeasuredHeight();
                }
                getChildAt(i).layout(left, top, right, top + view.getMeasuredHeight());
                left += view.getWidth();
            }
        }
    
    
        //测量
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
            }
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            Log.i("sj", "onTouchEvent: ----" + "View Group---消费事件");
            return super.onTouchEvent(event);
    //        return true;
    //        return false;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            Log.i("sj", "onInterceptTouchEvent: ----" + "View Group---拦截事件【return super】 不拦截");
            return super.onInterceptTouchEvent(ev);
    //        return true;
    //        return false;
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            Log.i("sj", "dispatchTouchEvent: ----" + "View Group---的事件分发  【return super】 往下传");
            return super.dispatchTouchEvent(ev);
    //        return true;
    //        return false;
        }
    }
    
    // 4. 自定义View代码
    package com.example.mybase_one;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.PointerIcon;
    import android.widget.Button;
    import android.widget.TextView;
    
    import androidx.annotation.Nullable;
    
    @SuppressLint("AppCompatCustomView")
    public class Mybutton extends TextView {
        public Mybutton(Context context) {
            super(context);
        }
    
        public Mybutton(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public Mybutton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    //        super(context, attrs, defStyleAttr);
            this(context, attrs, defStyleAttr, 0);
        }
    
        @SuppressLint("NewApi")
        public Mybutton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        public CharSequence getAccessibilityClassName() {
            return Button.class.getName();
        }
    
        @SuppressLint("NewApi")
        @Override
        public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
            if (getPointerIcon() == null && isClickable() && isEnabled()) {
                return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
            }
            return super.onResolvePointerIcon(event, pointerIndex);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            Log.i("sj", "dispatchTouchEvent: ----"+"View---的事件分发 【return super】 接收事件");
            return super.dispatchTouchEvent(event);
    //        return true;
    //        return false;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            Log.i("sj", "onTouchEvent: ----"+"View---消费事件 【return super】 给了onclick自己消费事件");
            return super.onTouchEvent(event);
    //        return true;
    //        return false;
        }
    }
    
    
    在这里插入图片描述

    代码全部在上面了,可以测试了!

    首先看下正常情况下,给按钮点击事件的顺序


    在这里插入图片描述

    仔细看的朋友会发现为什么activity,viewgroup,viewd的dispatchTouchEvent执行了两遍呢?
    这是因为ACTION_DOWN和ACTION_UP分别是两个事件!

    分发方法dispatchTouchEvent
    从方法的名称中可以看出该方法主要是负责分发,是安卓事件分发过程中的核心。事件是如何传递的,主要就是看该方法,理解了这个方法,也就理解了安卓事件分发机制。

    1. Activity的dispatchTouchEvent方法
      这里我们改下返回值!


      在这里插入图片描述
      在这里插入图片描述

      事件没有到ViewGroup层!这里我不一一粘贴了,不然会太长!
      总结:
      只有返回super时候事件才会传递
      返回true以及false都自己消费

    2. ViewGroup的dispatchTouchEvent方法


      在这里插入图片描述
    3. View的dispatchTouchEvent方法

    以上以此类推!测试!结果是


    在这里插入图片描述
    • 仔细看的话,图分为3层,从上往下依次是Activity、ViewGroup、View
    • 事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发
    • 箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
    • dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
    • 目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。
    • 之前图中的Activity 的dispatchTouchEvent 有误(图已修复),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

    仔细看整个图,我们得出事件流 走向的几个结论(希望读者专心的看下图 1,多看几遍,脑子有比较清晰的概念。)

    1. 如果事件不被中断,整个事件流向是一个类U型图,我们来看下这张图,可能更能理解U型图的意思。
      在这里插入图片描述
      所以如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity---->ViewGroup--->View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View--->ViewGroup--->Activity从下往上调用onTouchEvent方法。
      2、dispatchTouchEvent 和 onTouchEvent 一旦return true,事件就停止传递了(到达终点)(没有谁能再收到这个事件)。 看下图中只要return true事件就没再继续传下去了,对于return true我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能再收到这个事件了。
      在这里插入图片描述
      3、dispatchTouchEvent 和 onTouchEvent return false的时候事件都回传给父控件的onTouchEvent处理。
      在这里插入图片描述
      看上图深蓝色的线,对于返回false的情况,事件都是传给父控件onTouchEvent处理。
    • 对于dispatchTouchEvent 返回 false 的含义应该是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传直
      到某个onTouchEvent return true),事件分发机制就像递归,return false 的意义就是递归停止然后开始回溯。
    • 对于onTouchEvent return false 就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。
      4、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
      ViewGroup 和View的这些方法的默认实现就是会让整个事件安装U型完整走完,所以 return super.xxxxxx() 就会让事件依照U型的方向的完整走完整个事件流动路径),中间不做任何改动,不回溯、不终止,每个环节都走到。

      在这里插入图片描述
      所以如果看到方法return super.xxxxx() 那么事件的下一个流向就是走U型下一个目标,稍微记住上面这张图,你就能很快判断出下一个走向是哪个控件的哪个函数。

    5、onInterceptTouchEvent 的作用

    在这里插入图片描述
    Intercept 的意思就拦截,每个ViewGroup每次在做分发的时候,问一问拦截器要不要拦截(也就是问问自己这个事件要不要自己来处理)如果要自己处理那就在onInterceptTouchEvent方法中 return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent传递。

    6、ViewGroup 和View 的dispatchTouchEvent方法返回super.dispatchTouchEvent()的时候事件流走向。

    在这里插入图片描述

    首先看下ViewGroup 的dispatchTouchEvent,之前说的return true是终结传递。return false 是回溯到父View的onTouchEvent,然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return true和false 都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent,记住这一点。
    那么对于View的dispatchTouchEvent return super.dispatchTouchEvent()的时候呢事件会传到哪里呢,很遗憾View没有拦截器。但是同样的道理return true是终结。return false 是回溯会父类的onTouchEvent,怎样把事件分发给自己的onTouchEvent 处理呢,那只能return super.dispatchTouchEvent,View类的dispatchTouchEvent()方法默认实现就是能帮你调用View自己的onTouchEvent方法的。

    说了这么多,不知道有说清楚没有,我这边最后总结一下:

    • 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
    • ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
    • ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
    • View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。

    ViewGroup和View 的dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标

    注:------> 后面代表事件目标需要怎么做。
    1、 自己消费,终结传递。------->return true ;
    2、 给自己的onTouchEvent处理-------> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
    3、 传给子View------>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。
    4、 不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent------->return false;
    注: 由于View没有子View所以不需要onInterceptTouchEvent 来控件是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent处理(相当于拦截),对比ViewGroup的dispatchTouchEvent 事件分发,View的事件分发没有上面提到的4个目标的第3点。

    ViewGroup和View的onTouchEvent方法是做事件处理的,那么这个事件只能有两个处理方式:

    1、自己消费掉,事件终结,不再传给谁----->return true;
    2、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;View的默认实现是不消费的。所以super==false。

    ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:

    1、拦截下来,给自己的onTouchEvent处理--->return true;
    2、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;

    相关文章

      网友评论

          本文标题:Android 事件分发笔记

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