Android:View的事件分发

一、概述

1、了解并掌握View的事件分发,可以让我们解决一些view的事件冲突和做一些自定义view的事件。我将以测试代码打印的log的形式去跟踪并了解事件分发的过程。了解事件分发,需要了解view三个重要的函数。

public boolean dispatchTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev)(view的这个方法,viewGroup有)
public boolean onTouchEvent(MotionEvent event) 

viewGroup和view以树的结构组织起来,当手指触摸view的时候,将或触发这三个方法,按照某些规则,进行事件的传递分发。而我们所要知道的就是这些规则,来知道为什么,view的事件何时被拦截了,父view为何没有触发事件。

 

二、测试过程

首先定义两个viewGroup

public class ViewGroupOne extends LinearLayout {

    String TAG = "ViewGroupOne";


    public ViewGroupOne(Context context) {
        super(context);
    }

    public ViewGroupOne(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constants.LOG_TAG,TAG+":"+"onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
        return super.onTouchEvent(event);
    }
}
public class ViewGroupTwo extends LinearLayout {

    String TAG = "ViewGroupTwo";


    public ViewGroupTwo(Context context) {
        super(context);
    }

    public ViewGroupTwo(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(Constants.LOG_TAG,TAG+":"+"onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
        return super.onTouchEvent(event);
    }
}

再定义一个view

public class ViewOne extends AppCompatButton {

    String TAG = "ViewOne";


    public ViewOne(Context context) {
        super(context);
    }

    public ViewOne(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_UP");
                break;
                default:
        }
        return super.onTouchEvent(event);
    }
}

将他们嵌套起来,看看他们的事件分发

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <dome.cjk.domeproject.viewTest.ViewGroupOne
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent">
        <dome.cjk.domeproject.viewTest.ViewGroupTwo
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <dome.cjk.domeproject.viewTest.ViewOne
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="点击我"
                android:id="@+id/btn_testview"/>
        </dome.cjk.domeproject.viewTest.ViewGroupTwo>
    </dome.cjk.domeproject.viewTest.ViewGroupOne>
</android.support.constraint.ConstraintLayout>

好了,我们结构树组织起来了,点击ViewOne打印下日志看看viewGroup和view之间这三个函数是按照什么顺序来执行的。

01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 14:56:41.235 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 14:56:41.242 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_MOVE
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 14:56:41.260 22319-22319/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_UP

 

在默认的情况下,我们点击一个view发生了以下过程,所以是最上层的view(最上层的是PhoneWindow)接收到view的按下事件ACTION_DOWN,

然后依次,从上至下传递子viewGroup或view,触发dispatchTouchEvent《 onInterceptTouchEvent,直到最底层的view的dispatchTouchEvent《 onTouchEvent。。。最终默认被view消费。

其实这个分发过程跟dispatchTouchEvent和onInterceptTouchEvent、onTouchEvent的分发过程的返回值有很大的关系。返回代表消费这个事件了,不会往后传了,返回false代表没有消费这个事件,可以继续往下面传。当onTouchEvent返回为true的时候,表示被当前的view消费掉,而让不再父ViewGroup去处理,默认情况下,view的onTouchEvent返回为true,不会往父ViewGroup传,如果返回为false的话,事件没有被消费掉,而依次往上,被父ViewGroup消费,父ViewGroup不消费,就让父ViewGroup的父ViewGroup去消费。让我们修改一下代码验证上面的onTouchEvent消费的过程。

修改一下viewOne的OnTouchEvent的返回值为false

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.i(Constants.LOG_TAG,TAG+":"+"ACTION_UP");
            break;
            default:
    }
    return false;
}

重新再点击viewOne打印下日志

01-17 15:27:50.053 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onTouchEvent
01-17 15:27:50.054 25579-25579/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent

 

父view依次都执行了onTouchEvent。

修改一下viewGroup的

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.i(Constants.LOG_TAG,TAG+":"+"onTouchEvent");
    return true;
}

打印

01-17 15:32:17.399 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 15:32:17.400 26214-26214/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onTouchEvent

 

事件在ViewGroupTwo消费掉了,ViewGroupOne就没有走onTouchEvent了。很多事件冲突问题,就是因为被子view消费掉了,而父view就得不到的方法了。

 

那可不可以在子view触发onTouchEvent之前通过onInterceptTouchEvent就拦截事件不再往下传呢?答案是肯定的,从onInterceptTouchEvent的字面意思,我们都可以知道,他是用来拦截TouchEvent的方法的。通过onInterceptTouchEvent也可以消费事件。onInterceptTouchEvent默认为false,表示不中断,而为true的话表示中断,我们先修改一下ViewOne的onInterceptTouchEvent的返回值为true,看下了打印了什么。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.i(Constants.LOG_TAG,TAG+":"+"onInterceptTouchEvent");
    return true;
}

打印

01-17 15:43:26.770 28718-28718/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:43:26.770 28718-28718/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:43:26.770 28718-28718/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent

拦截了直接去先让走自己onTouchEvent方法,不让其子view去触发消费功能了。

在action_down dispatchTouchEvent返回true的时候表示消费这个事件,表示不再往上面传递

那修改dispatchTouchEvent的返回值又是发生了什么呢,让我们拭目以待

修改一下

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
    super.dispatchTouchEvent(ev);
    return true;
}

 

打印

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.i(Constants.LOG_TAG,TAG+":"+"dispatchTouchEvent");
    super.dispatchTouchEvent(ev);
    return true;
}
01-17 15:53:58.360 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onInterceptTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupTwo:dispatchTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onInterceptTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewOne:dispatchTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewOne:onTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewOne:ACTION_DOWN
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupTwo:onTouchEvent
01-17 15:53:58.361 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
01-17 15:53:58.363 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.363 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent

第二次打印

01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:dispatchTouchEvent
01-17 15:53:58.364 32168-32168/dome.cjk.domeproject I/doem_test: ViewGroupOne:onTouchEvent

直接走了ViewGroupOne的onTouchEvent方法。说明dispatchTouchEvent方法一定程度上可以拦截事件的。

假如某一个view在ontouchevent那里消费了,当action_up的时候,事件dispathchTouchEvent传到消费的view的ontouchview那里去,就不往下传了。

对于ACTION_MOVE和ACTION_UP在不同函数中的传递,有以下结论:

  • 结论1:对于dispatchTouchEvent :返回值为true时,自己消费事件。因为返回值为true代表消费,事件不会往下面传,因此ACTION_DOWN事件传递到此处停止传递,ACTION_MOVE和ACTION_UP也传递到此处停止向下传递,这个时候传递方向是一致的。

  • 结论2:对于onTouchEvent :返回值为true时,自己消费事件。因为事件传递到onTouchEvent有可能是下层View或ViewGroup回传过来的,这时候ACTION_DOWN是经过下层传递回来的,但是此时ACTION_MOVE和ACTION_UP并不会传递到下层;也有可能是自身的onInterceptTouchEvent 返回了true传递过来的,这个时候ACTION_MOVE和ACTION_UP和ACTION_DOWN事件的传递流程也是一样的。

对于ACTION_MOVE、ACTION_UP终极总结:
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。

网上有篇文章https://www.jianshu.com/p/e99b5e8bd67b写得挺好了。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值