自定义View 三——事件分发

自定义View 分发事件处理

叨叨

每定义一个View除了绘制我们还需要处理他的点击事件,特别是继承View而非其他控件的时候。

同时,我们应该知道在屏幕上我们是一层套一层,那么当我们屏幕点击下去的时候,这个时候应该是哪一个去处理?这里就有了我们的分发事件。

先看一张图。
概念图
如何产生的分发事件呢?就是在我们点下屏幕的那一刻,android给我们封装好了MotionEvent事件,有四个基本值用到:

public static final int ACTION_CANCEL = 3;
public static final int ACTION_DOWN = 0;
public static final int ACTION_HOVER_MOVE = 7;
public static final int ACTION_MOVE = 2;

cancel产生是非人为。。。

down是事件开始的时候,我们点下屏幕的那一刻就是 down

move是我们在屏幕上滑动时产生的事件

updown对应,是抬起手指时事件


一般来说,我们点击到哪一个View就哪一个view去处理(消费)这些事件,所以在相应的View(特别是自定义view,ps非ViewGroup派生类)覆写 onTouchEvent里的方法。但是既然有事件分发,那肯定存在某一个view or viewgroup中途拦截消费了这个事件。

比如,最常见的下拉刷新控件,我们在监听是否滑动到最顶层,就使用RecyclerView(ListView 或其他)父布局的刷新逻辑,这里产生的就是,当我们手指点下时,判断recyclerview是否在最顶层,并选择刷新控件是否处理(消费)该事件。如果消费了那recycler就接受不到事件也消费不了,反之就是recycler处理(消费)。

上面一段话就是,事件分发逻辑是这样的

activity –> viewgroup –> view

别问我activity哪来的,最外面那层不是activity么?你要说Fragment?它有这些事件覆写功能么?


割掉,分发过程一般有三个方法

表示有该方法,X表示无啦

类型 相关方法 Activity ViewGroup View
事件分发 dispatchTouchEvent
事件拦截 onInterceptTouchEvent X X
事件消费 onTouchEvent

按照顺序来,应该这样:

  1. Activity对点击事件的分发机制
  2. ViewGroup对点击事件的分发机制
  3. View对点击事件的分发机制

所以,我们需要了解每一个部分如何处理的,才能够在我们自己View里面去灵活使用。

搬一下图片,很清晰的表示了事件是怎么分发以及处理(消费)

搬运地址HERE
事件分发

  • 对于 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点。

搬运完了.


我们上文知道,我们一般处理事件有down,move,up三种。
上面的图文一般针对的ACTION_DOWN处理,那么,move和up是这样么?

了解一下,down事件如果在没有消费,那么就会按照上文图中的步伐调用回溯直到被消费的那一层,而moveup两个将会在直接在dispatch这调用onTouchEvent也就是说,如果某一层消费了事件,那么三者的步伐不一定是相同的,特别是在消费之前的分发操作里没有return false的存在。


实践一下,PS:这里的一切操作均为emmmm,单指操控的。

我们需要一个完整的流程,就是需要三个家伙:ActivityViewGroupView

Activty:

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

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

Group:

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

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

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

CView:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.d("CView", "dispatchTouchEvent:" + event.getAction());
    return super.dispatchTouchEvent(event);
}

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

XML布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">

<cn.krisez.myapplication.Group
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="#000">
    <cn.krisez.myapplication.CView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#ffff00"/>
</cn.krisez.myapplication.Group>

</RelativeLayout>

看一下具体内容

布局

ok,我们点击一下,注意,点黄色区域。那里才有我们的View。

MainActivity: dispatchTouchEvent:0
Group: dispatchTouchEvent:0 onInterceptTouchEvent:0
CView: dispatchTouchEvent:0  onTouchEvent:0
Group: onTouchEvent:0
MainActivity: onTouchEvent:0
MainActivity: dispatchTouchEvent:1
MainActivity: onTouchEvent:1

我们看到我们最后还有一个dispatchTouchEvent:1

这是哪来的?看一下MotionEvent的源码不就知道了?

public static final int ACTION_UP = 1;

ok,相信你也猜到了,我们没做任何筛选处理,所以有一个UP的EVENT出现。那么,我们也可以提起一个疑问。为什么只有Activity有ACTION_UP的事件,另外几个没有,不是都没任何处理么?

这里就要想到我们的分发机制了,当我们事件产生的时候,顺序是这样的:

Activity -> ViewGroup -> View

如果这之间我们没有对事件消费,那么在走到相应的View的onTouchEvent时就会将事件回传。直到某一个消费。那么这里我们没有处理,所以我们的事件最终回到了Activity这里,相当于Activity进行消费了,那么,ACTION_MOVE 和 ACTION_UP也就不会走DOWN的路径,而是直接dispatch -> touchEvent

如果我们在ViewGroup消费了,up怎么走?

改变一下代码。

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d("Group", "onTouchEvent:" + event.getAction());
    return true;
}

我们直接返回TRUE。看一下LOG

MainActivity: dispatchTouchEvent:0
Group: dispatchTouchEvent:0
Group: onInterceptTouchEvent:0
CView: dispatchTouchEvent:0
CView: onTouchEvent:0
Group: onTouchEvent:0
MainActivity: dispatchTouchEvent:1
Group: dispatchTouchEvent:1
Group: onTouchEvent:1

在这里我们看到,DOWN按照顺序走到了Group这里就停了,那么我们的UP就会走到Group的分发然后直接在Group的onTouchEvent里进行消费了。

从这里我们可以得到一个结论,当DOWN在某一个地方进行消费,那么,MOVE 和 UP 会直接从dispatch分发到相应那一层的onTouchEvent


在这里,我们实验一个从左向右划的操作,进行该界面的FINISH();

我们要这样想,我们需要的是Activity的finish(),如果其他界面也有相应的操作,岂不是回传的时候就拦截了,然后我的Move就没了么?所以我们要在activity的dispatchTouchEvent方法里进行操作判断,如果是从左到右的直接finish()调,同时返回true,不进行下级分发了。

或者说,我们自定义一个ViewGroup进行拦截判断,然后在onTouchEvent里操作,就命名为MFView吧。

private int xx = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
   int action = ev.getAction();
   int x = (int) ev.getX();
   switch (action){
       case MotionEvent.ACTION_DOWN:
           xx = x;
           break;
           case MotionEvent.ACTION_MOVE:
               if(xx - x > 50){
                   Log.d("Group", "onInterceptTouchEvent:" + "finish()");
                   return true;
               }
               break;
   }
    return false;
}

ok,实验一下,首先我要表明我要处理事件。所以在onTouchEvent里返回true。这里我们上面处理了,就不管了。不然直接传回给了activity就拿不到move和up事件了。

onInterceptTouchEvent:finish()

在这里有一个坑,就是如果其子view没有任何处理,就是默认的话,那么在这里ViewGroup的onInterceptTouchEvent:finish()方法不会执行move和up事件。

为什么?

了解一下,如果某一层消费了该事件且其所有子View没有一个有对事件消费的存在,那么在该View里,将在dispatch直接传递给onTouchEvent。所以,我们还要讲CView改一下。

@Override
public boolean onTouchEvent(MotionEvent event) {
    return true;
}

再次了解下原因,当子View存在消费,那我们就需要将事件从activity一层层传递下来,在这个过程中我们会遇到viewgroup这一层,传递的时候,我们就需要再次拦截看是否需要group自己处理。所以,在group中进行从左往右滑的时候,除非子view就消费事件,不然拦截器不起任何作用。所以建议这一个手势还是放在activity里面。同时为了防止某一层消费了onTouchEvent,所以我们手势放在activity的
dispatchTouchEvent方法里。

ok。学习完了。

猜你喜欢

转载自blog.csdn.net/qq_34206863/article/details/81778540