简单例子了解View的事件分发

什么是事件分发

我们在写自定义ViewGroup或者自定义View的时候经常要处理用户的点击事件,如果我们的View在最底层,他在很多ViewGroup里面,我们如何让我们的点击事件准确传递到View里面,这就涉及到一个View很重要的知识点,View的事件分发。事件分发,分开来讲就是事件+分发,所谓事件指的就是View的被手机触摸后产生的事件MotionEvent,而分发指的就是MotionEvent的传递和处理。
下面,我们说一下单手指触摸事件有哪些

ACTION_DOWN——手指刚触摸屏幕
ACTION_MOVE——手指在屏幕上移动
ACTION_UP———手指从屏幕上松开的一瞬间

事件讲完了,我们接下来说一下分发过程中涉及到的方法

dispatchTouchEvent(MotionEvent ev)
onInterceptTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent event)

所以事件分发,结合这些代码就是每一个ACTION皆会触发那些方法。我们在要做就是根据需求来决定那个事件分发到那层,以及搞清楚为什么会这样分发。
接下来,我们通过一个例子来仔细讲讲这三个方法以及上述三个事件。

简单的例子了解事件分发

测试的例子如上,我们编写三个自定义view来演示这个效果,第一个是ViewGroupA,也就是最外层的绿的,第二个是ViewGroupB,也就是中间的蓝的,然后是最里面的黑色的View。XML布局如下:

   <com.byhieg.viewdipatch.custormview.ViewGroupA
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@android:color/holo_green_light">

        <com.byhieg.viewdipatch.custormview.ViewGroupB
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@android:color/holo_blue_light">
        <com.byhieg.viewdipatch.custormview.ViewTest
            android:layout_width="100dp"
            android:layout_height="100dp" />
        </com.byhieg.viewdipatch.custormview.ViewGroupB>

    </com.byhieg.viewdipatch.custormview.ViewGroupA>

ViewGroupA 里面放入子View ——ViewGroupB 然后ViewGroupB放入子View-ViewTest
三者的代码如下:
ViewGroupA

public class ViewGroupA extends ViewGroup{

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

    public ViewGroupA(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }



    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        for(int i = 0;i < childCount;i++) {
            View child = getChildAt(i);
            child.layout(0,0, Change.dip2px(getContext(),200),Change.dip2px(getContext(),200));

        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("ViewGroupA","ViewGroupA dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("ViewGroupA","ViewGroupA onInterceptTouchEvent" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("ViewGroupA","ViewGroupA onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}

ViewGroupB:

public class ViewGroupB extends ViewGroup{

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

    public ViewGroupB(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }



    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        for(int i = 0;i < childCount;i++) {
            View child = getChildAt(i);
            child.layout(0,0,getMeasuredWidth(),getMeasuredHeight());
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("ViewGroupB","ViewGroupB dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("ViewGroupB","ViewGroupB onInterceptTouchEvent" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("ViewGroupB","ViewGroupB onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}

ViewTest

public class ViewTest extends View{

    private Paint paint;

    public ViewTest(Context context) {
        super(context);
        init();
    }

    public ViewTest(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ViewTest(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(10);
        paint.setStyle(Paint.Style.FILL);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0,0, Change.dip2px(getContext(),100), Change.dip2px(getContext(),100),paint);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("ViewTest","View dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("ViewTest","View onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}

根据我们写入的Log,当我们点击最里面的ViewTest的时候,我们会看到如下的Log输出

08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0

这些方法默认返回时false 然后按照如下传递原则:

在事件分发的时候,最外层的ViewGroup首先调用dispatchTouchEvent()方法,然后再执行onInterceptTouchEvent()方法,而View是没有onInterceptTouchEvent()的方法的,在传递的时候,如果这些方法返回的是false,则表示不拦截,事件会下面传递。我们在覆写这些方法的时候,不作处理,默认返回时false,所以我们可以看到事件传递到了ViewTest的dispatchTouchEvent(),但注意dispatchTouchEvent()与onInterceptTouchEvent()的区别,如果事件传递到了这个View,则dispatchTouchEvent()方法一定会调用,而onInterceptTouchEvent()方法则在dispatchTouchEvent()内部调用,表示是否拦截事件,所以当我们需要拦截的时候一般改写onInterceptTouchEvent()
在事件处理的时候,则是从分发到了最底层的View开始向上处理,在onTouchEvent(),返回了true,则表示这个View已经处理了 ,不必在向上传递,但我们覆写这些方法的时候,不作处理,默认返回时false,所以继续向上传递,到了最上层的ViewGroup中。这就是我们验证的结果。

这种结果我们可以用现实中的例子来解释,这个例子是网上看到了,很生动形象。我们把整个事件传递的View看成是一个公司,ViewGroupA是一个总经理,ViewGroupB是一个部长,ViewTest是一个员工。现在来了一个任务,总经理觉得不需要自己处理,返回了false,就传递给了下一层部长,部长看了也觉得不需要处理,继续返回false,传递给了底层员工,员工做了做,发现太难了,处理不了,就返回了false,然后上层部长接手,发现确实很难,也处理不了,继续返回false,传递给了总经理。整个事件分发处理就结束了。
现在,又来了一个新任务,我们总经理有了前车之鉴,决定自己处理这件事,不在交给下面的人做,因为给了他们,他们也处理不好,所以他决定自己拦截这个事件,于是在ViewGroupA中的onInterceptTouchEvent()中返回true,查看效果

08-30 08:53:31.125 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 08:53:31.126 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 08:53:31.126 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0

确实如我们之前所说的,是这样一个处理流程,ViewGroupA自己弄完了所有事情,随着事件的变多,总经理终于累倒了,于是他决定把事情分给部长,自己只处理部长处理不了的,于是我们总经理不拦截事件,而是部长拦截事件,我们看看效果

08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0

确实,事件分发到了部长,而部长也如愿的没处理好事件,传递给了总经理,如此以往,因为处理如此多的事件,总经理的病再也没好。于是,部长,和底层员工决定好好提高自身水平,不在把事件传递给总经理,于是,我们在底层员工ViewTest的onTouchEvent()返回true,表示他已经处理好事情,我们看看效果

08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0

这是一部分的Log,后面还有,不过我们先讲解上半部分,在这里,我们看到事件处理到ViewTest就为止了,所以事件处理没有传递到ViewGroupB,当然有些问题,确实底层员工处理不了,于是,我们在ViewGroupB的onTouchEvent()返回true。

08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0

这样,事件确实没有在传递给总经理。底层员工,部长已经足够胜任工作,总经理因为没有杂事压身,身体也逐渐康复,这家公司正常运转。Happy Ending

更多规则

在view进行事件处理的时候,如果他设置了onTouchListener,那么onTouchListener中的onTouch()方法将被调用,这时,我们需要根据onTouch()的返回值来决定onTouchEvent会不会调用,如果返回的是false,则表示会继续调用onTouchEvent,返回的是true,则不会。下面我们验证一下:返回为true的时候。
在MainActivity中,设置viewTest的事件,代码如下:

ViewTest view = (ViewTest)findViewById(R.id.viewTest);
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.e("onTouch","我中弹了");
                return true;
            }
        });
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/onTouch: 我中弹了

可以看出没有打印出viewTest的onTouchEvent日志,证明确实没有调用,现在设置返回为falseonTouchEvent被调用的日志会出现,受限于篇幅,结果就不放出来了。
接下来,我们在ViewTest的onTouchEvent中设置一个点击监听,查看一些好玩的东西。首先,我先设置我们的ViewTest为可点击,viewTest的onTouch返回值也是false,然后我们看一下onTouchEvent代码:

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("ViewTest","View onTouchEvent" + event.getAction());
        Log.e("More","我被调用");
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e("ViewTest","原来中弹的不是我");
            }
        });
        return false;
    }

这时点击我们的viewTest,我们发现他竟然没有点击事件的日志。这是因为我们直接返回的false,截断了后面的点击事件的触发,所以决定什么都不做,采用默认的实现,即

return super.onTouchEvent(event);

神奇的事情发生了,出现了该有的点击效果。我们查看Log

08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/onTouch: 我中弹了
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/More: 我被调用
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/onTouch: 我中弹了
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/More: 我被调用
08-30 13:07:04.932 16083-16083/com.byhieg.viewdipatch E/ViewTest: 原来中弹的不是我

这是完整的调用日志,我们可以仔细看一下,会发现出现点击事件的日志在最后一行,而之前重复出现了ViewGroupA和ViewGroupB的事件分发。是时候揭露出完整的触发过程了。

当我们手指按下时候,会触发ACTION_DOWN的事件,这时会进行一系列的事件分发,分发规则如上面所讲,当我们手指松开的时候,会触发ACTION_UP的事件,这同样会进行一系列的事件分发,最后当这些都弄完之后,会触发onClick()事件,也就是说onClick()是事件触发的尾端。一旦前面决定不继续传递事件,则onClick()事件就不会触发。

所以这里面有一个概念叫做同一事件序列:指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数个move事件,最终以up事件结束。
简而言之就是down-> move-> move-> move-> move......->up
而上面我们提到的onclick()如果大家看源码的话,会发现他是ACTION_UP之后通过performClick()调用的。所以如果view设置了点击事件并且能向后传递,则会最后调用onClick()。
如果仔细看上面的日志,会发现我们的打印日志的代码是这样写的Log.e("ViewTest","View onTouchEvent" + event.getAction());日志最后会出现事件的名字,在日志里面,由于getAction返回的int型的静态常量,所以是用0, 1表示,0代表ACTION_DOWN,1代表ACTION_UP,所以通过这个日志,我们可以验证这个同一事件序列这个过程。前面我放出的日志实际也是不全 我也只是把DOWN的会就放出来,日志后面还有UP的事件,处于篇幅就不全放了而已。
而针对同一事件序列 上面的例子还会说明一个特性

注意,一开始,我们直接将onTouchEvent(MotionEvent event)返回值设置为false,这样他会截断后面ACTION_UP事件的触发,通俗来讲,就是传递到VIew的DOWN事件,他都没有消耗他,没处理好他,返回了false,让他上层处理,则同一事件序列的MOVE,UP也不会给他。结合我们之前公司的例子,就是这个DOWN事件都办不好,后面也不会给他其他任务了。

我们验证一下:

08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/onTouch: 我中弹了
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/More: 我被调用
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0

这是全部的日志,确实没有UP事件。

复杂的规则

下面的规则是出自《Android开发艺术探索》,我们对这些规则进行讲解,验证

  1. 同一事件序列是指从手机接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一些列事件,这个事件序列以DOWN事件开始,中间含有数个MOVE事件,最终以UP事件结束
  2. 某个View一旦决定拦截,那么这一个事件序列都只能有他处理,并且它的onInterceptTouchEvent不会被调用
  3. 正常情况下,一个事件序列只能被一个View拦截且消耗。
  4. 某个View一旦开始处理事件,如果他不消耗ACTION_DOWN,那么同一事件序列中其他事件不会交给他处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用
  5. 如果View不消耗除了ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续受到后续的事件,最终这些消失的点击事件会传递给Activity处理
  6. ViewGroup默认不拦截任何事件。
  7. View没有onInterceptTouchEvent方法,一旦有点击事件传递给他,那么他的onTouchEvent方法会被调用
  8. View的onTouchEvent默认会消耗事件,除非他是不可点击的。
  9. View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要他的clickable或者longClickable有一个true,那么他的onTouchEvent返回true。
  10. onClick会发生的前提是当前View是可点击的,并且他收到了down和up事件。

我们分别讲解和验证一下大神总结这些结论。
第一条,之前已经讲过了,就不说了
第二条,一旦View决定拦截,则表明这个View要自己上手处理这个事件,那他势必不会再将事件传递下去,自然也不会调用onInterceptTouchEvent
第三条,一般,一个事件序列:down-> move-> move-> move-> move......->up只会交给一个View处理,因为第二条,一旦决定拦截,则表明他要自己上手处理这个。特殊情况会通过onTouchEvent强行传递给其他View,这也是书中原话,但至今不清楚怎么强行传递给其他View。
第四条,这句话的意思就是如果DOWN事件返回的是false,则UP事件和MOVE事件也不会用来处理。而且会把事件交给父元素去处理,刚才的日志,已经验证了这点。
第五条,如果DOWN事件返回的是true,而MOVE事件返回的false,则这个事件会消失,父元素的onTouchEvent并不会调用,我们修改下我们的代码验证下:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("ViewTest","View onTouchEvent" + event.getAction());
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.e("ViewTest","View onTouchEvent" + event.getAction());
                return false;
            case MotionEvent.ACTION_UP:
                Log.e("ViewTest","View onTouchEvent" + event.getAction());
                return true;
        }
        return super.onTouchEvent(event);
    }

我们将代码改成这个样子,然后运行看日志:

08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-31 08:09:13.077 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent2
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent2
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent2
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent2
08-31 08:09:13.149 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent2
08-31 08:09:13.149 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent2
08-31 08:09:13.180 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent1

日志很长,因为不小心移动多了,触发了多个移动事件,我们会发现,满足了上述的条件,然后我们的父View确实没有触发onTouchEvent。斯国一,确实是这个样子的
第六条和第七条,上文提到过,这个不说了 ,因为ViewGroup的这个方法默认返回是false,默认不拦截。而View没有拦截方法,所以必须要处理。
第八条和第九条,处理事件的时候,view的onTouchEvent默认返回true,表示自己消耗这件事情,这个和我们上面说的默认返回时false是冲突的,这个是因为他后面补了一个条件,这它默认是不点击的,在我们上面的例子中,我们用的自定义的ViewTest,这个在没有引入讨论onClick的时候,他就是默认不可点击的,所以是false,当他可点击之后,我们在xml中设立了clickable属性,他的onTouchEvent则表示会消耗事件,返回是true,也可以理解嘛,设立了他可点击,就是想让他处理onclick事件。所以,一旦有一个Clickable或者longClickable为true,那么他的onTouchEvent就返回true。
第十条,onClick触发的前提是view点击,并且收到了down和up事件,这个上面也验证过了。

总结

这十条说完,发现很多东西,我们上文已经提到过,只是归纳的没有他好。他这十条,总体说来,就是
默认的事件传递会到底层的view,根据view的onTouchEvent方法中对DOWN事件的处理不同,会有不同的结果,如果返回了true,表示处理好了,则后续的MOVE,UP事件还会交给他来做,如果此外还设置了Clickable,则表示这个view也会响应onClick,而如果MOVE和UP返回了false,则会调用父View的onTouchEvent,如果返回了false,表示,这个view不堪重任,后续的MOVE和UP也不会给他做。
如果我们什么都不做,不去主动的更改onTouchEvent的返回结果,则会因为这个View是否能被点击来决定他的事件处理是否向上传递,如果能点击,则不传递,他自己来处理,如果不能点击,则返回false,向上传递。
至此,View事件的分发就到此结束了。后续可能会有根据源码去分析View分发的过程,这个就看事件啦。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Kotlin中,事件分发机制与Java中的事件分发机制类似。Kotlin中的事件分发机制是通过ViewGroup和View两个类来实现的。ViewGroup是一个容器,它可以包含多个View,而View则是一个具体的控件,例如Button、TextView等。 Kotlin中的事件分发机制主要包括三个阶段:捕获阶段、目标阶段和冒泡阶段。在捕获阶段中,事件从父容器向子View传递,直到找到目标View。在目标阶段中,事件被传递给目标View进行处理。在冒泡阶段中,事件从目标View向父容器传递,直到事件被处理或者传递到了最外层的容器。 Kotlin中的事件分发机制可以通过重写ViewGroup的dispatchTouchEvent()方法和View的onTouchEvent()方法来实现。在dispatchTouchEvent()方法中,可以根据需要对事件进行拦截或者传递给子View。在onTouchEvent()方法中,可以对事件进行处理。 以下是一个简单的Kotlin事件分发例子: ```kotlin class MyViewGroup(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) { override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { // 对子View进行布局 } override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { // 在这里可以对事件进行拦截或者传递给子View return super.dispatchTouchEvent(ev) } } class MyView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { override fun onTouchEvent(event: MotionEvent?): Boolean { // 在这里可以对事件进行处理 return super.onTouchEvent(event) } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值