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的down\move\up都能触发

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处理:

如果拦截,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);
}

动作由viewgroup的onTouchEvent处理

我们有这么一个需求,在自定义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事件了。

猜你喜欢

转载自www.cnblogs.com/suiyilaile/p/11135938.html