• Android 带你读懂事件分发


    工作有一段时间,有必要掌握事件传递的机制,最近研究了一下,记录下心得。
    1 Android中的事件
    android中触摸事件比较多,封装中MotionEvent类中,点击、触摸、滑动是我们常用的事件

    • MotionEvent.ACTION_DOWN
    • MotionEvent.ACTION_MOVE
    • MotionEvent.ACTION_UP
    • MotionEvent.ACTION_CANCEL

    2 核心方法
    了解传递机制前,我们需要先学习几个方法。
    2.1ViewGroup

    • dispatchTouchEvent 分发事件,默认接受事件并往下分发。如果返回true,拦截事件,不分发。下同。
    • onInterceptTouchEvent 预处理事件 默认返回false ,返回true表示拦截
    • onTouchEvent 处理事件 默认返回false ,不消费事件。如果在根布局或代码中设置了clickable=true,则super.onTouchEvent返回true,表示消费了事件。

    2.2View

    • dispatchTouchEvent
    • onTouchEvent 处理事件 返回值需要看view的属性clickable==true

    2.3View/ViewGroup 的 onTouchEvent返回值:

    • 如果view/viewGroup可点击的,比如Button,clickable = true,则super.onTouchEvent 返回true
    • 如果view/viewGroup不可点击,比如TextView,clickable = false,则super.onTouchEvent 返回false
    • 在xml中设置view/viewGroup clickable属性为true, 则该view:super.onTouchEvent 返回true

    2.4核心

    • clickable影响了super.onTouchEvent返回值
    • 如果返回false,View只能处理down事件,后续事件不会触发
    • 如果返回true,View的downmoveup都能触发

    3 事件传递
      一次完整的屏幕触摸事件:手指按下,滑动,抬起。这个事件由一个action_down,若干个action_move,和一个action_up组成。那么它在我们的屏幕中是如何传递的呢?
    默认情况下事件传递是从Activity到ViewGroup到View的过程,最终由View接受到。如图:

      

    我们定义一个MyViewGroup和一个MyView,打印其中的方法。
    MyViewGroup,clickable为false,super.onTouchEvent默认为false,不消费事件

    public class MyViewGroup extends FrameLayout {
    
        String Tag = "===MyViewGroup";
    
        public MyViewGroup(Context context) {
            super(context);
        }
    
        @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;
            }
            return super.dispatchTouchEvent(ev);
        }
    
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "onInterceptTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "onInterceptTouchEvent ACTION_UP");
                    break;
            }
            return super.onInterceptTouchEvent(ev);
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.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;
            }
            return super.onTouchEvent(ev);
        }
    }

    MyView 继承TextView,clickable默认false,super.onTouchEvent默认为false,不消费事件

    public class MyView extends android.support.v7.widget.AppCompatTextView {
    
        String Tag = "===MyView";
    
        public MyView(Context context) {
            super(context);
        }
    
        @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;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.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;
            }
            return super.onTouchEvent(ev);
        }
    }

    activity布局中引用

    <com.zcwipe.frecyclerviewdemo.MyViewGroup
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <com.zcwipe.frecyclerviewdemo.MyView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/color_theme"
            android:gravity="center"
            android:text="content" />
    </com.zcwipe.frecyclerviewdemo.MyViewGroup>

    事件都是由action_down开始,
    首先activity执行super.dispatchTouchEvent分发给viewgroup。
    然后viewgroup执行super.dispatchTouchEvent,super.onInterceptTouchEvent。
    最后view执行super.dispatchTouchEvent,onTouchEvent。这个时候view在onTouchEvent里就接受到了action_down事件。

    情况1:
    如果view不在onTouchEvent里消费action_down事件(默认返回false,不消费),那么这个action_down事件就会交由父布局,也就是viewgroup的onTouchEvent来处理。如果viewgroup也不处理。就会继续交给上层布局activity的onTouchEvent处理action_down事件。
    此时完成了action_down事件的传递。由于view和viewgroup都没有处理action_down事件,系统就不会再传递后继事件action_move,action_up.日志输出

    后续的action_move,action_up事件不再传递给ViewGroup和View。直接由activity处理。

    情况2:
    我们在view的action_down事件里返回true,消费了这个事件,(也可以定义一个button,或者设置clickable=true,默认消费事件)那么action_down事件便不会传递给viewgroup及activity,那后续的事件都由谁来处理?
    action_down是由上层传递到view中的,后续动作action_move和action_up 也是如此。由于我们在子view的down中消费了事件,那么viewgroup在传递后续事件时会在onInterception方法中判断是否需要拦截此action_move、action_up动作
    如果不拦截,viewgroup中onInterception不处理,动作由view的onTouchEvent处理。且每次传递move,up事件都会执行onInterception方法,决定是否拦截。日志输出:

    如果拦截,viewgroup的action_move返回true,拦截事件,

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(Tag, "onInterceptTouchEvent ACTION_MOVE");
                return true;
            case MotionEvent.ACTION_UP:
                Log.e(Tag, "onInterceptTouchEvent ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    后续move,up动作由viewgroup的onTouchEvent处理,且不再执行onInterception方法

    情况3:

    如果viewgroup拦截down事件,那么down事件不再传递给子view,直接由viewgroup的ontouch处理。在viewgroup的onTouchEvent事件中:

    如果消费了down事件,返回true,那么后续事件move,up都会直接交由viewgroup的onTouchEvent处理,且不再执行viewgroup的onInterceptTouchEvent方法。日志输出:

    如果没有消费,那么move,up将由上层view处理。

     案例:加入我们有这么一个需求,在自定义viewgroup中接受action_move事件,实现滑动效果,如何处理?

    由情况1我们知道,如果view不消费down事件,viewgroup也不消费事件,那么无法实现。
    由情况2我们知道,如果view消费了down事件,viewgroup也不拦截,那么也无法实现。
    实现方案两种:
    * view onTouchEvent中down消费,viewgroup:onInterceptTouchEvent中move拦截
    * view onTouchEvent中down不消费 ,viewgroup:onTouchEvent中down消费
    有时候我们不知道view是否消费了事件,那么使用以下方案写出健壮行代码,viewgroup中
    1. ontouchevent down消费事件
    2. onInterceptTouchEvent中move拦截

     @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "onInterceptTouchEvent ACTION_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    return true;
    //                break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "onInterceptTouchEvent ACTION_UP");
                    break;
            }
            return super.onInterceptTouchEvent(ev);
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.e(Tag, "onTouchEvent ACTION_DOWN");
                    return true;
                case MotionEvent.ACTION_MOVE:
                    Log.e(Tag, "onTouchEvent ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e(Tag, "onTouchEvent ACTION_UP");
                    break;
            }
            return super.onTouchEvent(ev);
        }

    这样可以稳定的在viewgroup中处理action_move事件,且不影响子view的点击事件。日志输出:

  • 相关阅读:
    PID算法控制直流电机笔记
    HAL库直流电机编码测速(L298N驱动)笔记
    HAl库控制L298N直流电机旋转笔记
    MQTT介绍与使用(转载)
    STM32中STD、HAL、LL库比较
    rpm检验是否被改动过
    find命令 参数
    会话管理测试时的注意点
    无权限修改用户组权限的状况一
    burpsuite + sqlmap 日志导出批量扫描
  • 原文地址:https://www.cnblogs.com/suiyilaile/p/11135938.html
Copyright © 2020-2023  润新知