事件分发

android中的事件分发、焦点问题。

啥都不管,这里先安利一个非常好用的方法。简直是解决事件冲突的利器啊。

getParent()(父亲).request(请求)Disallow(不)Intercept(拦截)TouchEvent(touch事件)(true(同意));
getParent().requestDisallowInterceptTouchEvent(true);

我是在自定义柱形图的左右滑动跟侧滑面板冲突的时候各种查阅找到的。灰常好用。

###OnTouch和OnTouchEvent

这2个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果再onTouch方法中通过返回true,那么事件就被消费掉,那么onTouchEvent将不会再执行.

另外需要注意的是,onTouch 能够得到执行需要两个前提条件,第一 mOnTouchListener 的值
不能为空,第二当前点击的控件必须是 enable 的。因此如果你有一个控件是非 enable 的,那么给
它注册 onTouch 事件将永远得不到执行。对于这一类控件,如果我们想要监听它的 touch 事件,就
必须通过在该控件中重写 onTouchEvent 方法来实现。

###Android 事件分发机制

Android 的事件分发机制主要是 Touch 事件分发,有两个主角:ViewGroup 和 View。Activity
的 Touch 事件事实上是调用它内部的 ViewGroup 的 Touch 事件,可以直接当成 ViewGroup 处理。
View 在 ViewGroup 内, ViewGroup 也可以在其他 ViewGroup 内, 这时候把内部的 ViewGroup
当成 View 来分析。
先分析 ViewGroup 的处理流程:首先得有个结构模型概念:ViewGroup 和 View 组成了一棵树
形结构,最顶层为 Activity 的 ViewGroup,下面有若干的 ViewGroup 节点,每个节点之下又有若
干的 ViewGroup 节点或者 View 节点,依次类推。如图:

当一个 Touch 事件(触摸事件为例)到达根节点,即 Acitivty 的 ViewGroup 时,它会依次下发,
下发的过程是调用子 View(ViewGroup)的 dispatchTouchEvent 方法实现的。简单来说,就是
ViewGroup 遍历它包含着的子 View,调用每个 View 的 dispatchTouchEvent 方法,而当子 View
为 ViewGroup 时,又会通过调用 ViwGroup 的 dispatchTouchEvent 方法继续调用其内部的 View
的 dispatchTouchEvent 方法。上述例子中的消息下发顺序是这样的:1-2-5-6-7-3-4.
dispatchTouchEvent 方法只负责事件的分发,它拥有 boolean 类型的返回值,当返回为 true 时,
顺序下发会中断。在上述例子中如果5的 dispatchTouchEvent 返回结果为 true,那么6-7-3-4
将都接收不到本次 Touch 事件。    
  1. Touch事件分发中有2个主角:ViewGroup和View。

    ViewGroup 包 含
    onInterceptTouchEvent 、 dispatchTouchEvent 、 onTouchEvent 三 个 相 关 事 件 。 View 包 含
    dispatchTouchEvent、onTouchEvent 两个相关事件。其中 ViewGroup 又继承于 View。

  2. ViewGroup 和 View 组成了一个树状结构

    ViewGroup 和 View 组成了一个树状结构,根节点为 Activity 内部包含的一个 ViwGroup

  3. 触摸事件由 Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,
    Down 和 Up 都只有一个,Move 有若干个,可以为 0 个。

  4. 当 Acitivty 接收到 Touch 事件时,将遍历子 View 进行 Down 事件的分发。ViewGroup 的遍
    历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的 View,这个 View 会81
    在 onTouchuEvent 结果返回 true。

  5. 当某个子 View 返回 true 时, 会中止 Down 事件的分发, 同时在 ViewGroup 中记录该子 View。
    接下去的 Move 和 Up 事件将由该子 View 直接进行处理。 由于子 View 是保存在 ViewGroup 中的,
    多层 ViewGroup 的节点结构时,上级 ViewGroup 保存的会是真实处理事件的 View 所在的
    ViewGroup 对象:如 ViewGroup0-ViewGroup1-TextView 的结构中,TextView 返回了 true,它将
    被保存在 ViewGroup1 中,而 ViewGroup1 也会返回 true,被保存在 ViewGroup0 中。当 Move
    和 UP 事件来时,会先从 ViewGroup0 传递至 ViewGroup1,再由 ViewGroup1 传递至 TextView。

  6. 当 ViewGroup 中所有子 View 都不捕获 Down 事件时,将触发 ViewGroup 自身的 onTouch
    事件。触发的方式是调用 super.dispatchTouchEvent 函数,即父类 View 的 dispatchTouchEvent
    方法。在所有子 View 都不处理的情况下,触发 Acitivity 的 onTouchEvent 方法。

  7. onInterceptTouchEvent 有两个作用:1.拦截 Down 事件的分发。2.中止 Up 和 Move 事件向
    目标 View 传递,使得目标 View 所在的 ViewGroup 捕获 Up 和 Move 事件

方法相关

  1. viewgroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent和onTouchEvent
  2. view的相关事件少一个onInterceptTouchEvent,只有dispatchTouchEvent和onTouchEvent两个方法.

onInterceptTouchEvent:拦截

dispatchTouchEvent : 事件分发

onTouchEvent: 消费处理

上图中不考虑拦截情况。事件分发顺序就是:1-2-5-6-3-4.跟上面的那种图是一样的。同样道理。假如6的 dispatchTouchEvent 返回true,那么3跟4就不会接收到。 规则:从外到里遍历所有的子view,遇到是viewgroup的遍历该viewgroup的所有子view

###demo实践

  1. 自定义一个button,继承自button,打印onTouch和dispatchTouchEvent中的log(这里咱不考虑move事件)

    public class MyButton extends Button {
        private static final String TAG = "MyButton";
    
        public MyButton(Context context) {
            super(context);
        }
    
        public MyButton(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    Log.i("marc","My-Button-onTouchEvent-Action_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
    //                Logger.e("My-Button-onTouchEvent-Action_MOVE");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.i("marc","My-Button-onTouchEvent-Action_UP");
            }
            return super.onTouchEvent(event);
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    Log.i("marc","My-Button-dispatchTouchEvent-Action_DOWN");
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                    Log.i("marc","My-Button-dispatchTouchEvent-Action_DOWN");
            }
            return super.dispatchTouchEvent(event);
        }
    }
    
  1. 该button在xml中使用。在activity中调用.

    mButton.setOnTouchListener(new View.OnTouchListener() {
             @Override
             public boolean onTouch(View v, MotionEvent event) {
                 switch (event.getAction()) {
                     case MotionEvent.ACTION_DOWN:
                         Log.i("marc",TAG + "-onTouch-Action_DOWN");
                         break;
                     case MotionEvent.ACTION_MOVE:
                         break;
                     case MotionEvent.ACTION_UP:
                         Log.i("marc",TAG + "-onTouch-Action_DOWN");
                 }
                 return false;
             }
         });
    
  2. 调用activity的dispatchTouchEvent方法

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.i("marc",TAG + "-dispatchTouchEvent-Action_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                Log.i("marc",TAG + "-dispatchTouchEvent-Action_DOWN");
        }
        return super.dispatchTouchEvent(event);
    }
    

log日志可以看到先后顺序

可以跟上面的相印证:从外向里依次分发

  1. down事件

    • 首先执行Activity的dispatchTouchEvent方法,进行事件的分发,actiivity就是上面2图中的viewgroup(不考虑顶层布局啥的),这个时候遍历它,activity中有个button的view。
    • 接着是button该view拿到该事件,然后执行dispatchTouchEvent进行分发。但是他自己是view,而不是viewgroup,没有子view
    • button就执行自己的onTouch事件(上面有说onTouch事件要早于onTouchEvent事件,若onTouch返回true,就不执行onTouchEvent)
    • 接着是执行onTouchEvent事件
  2. down事件结束,开始up事件,跟上面流程一样

    • 首先执行Activity的dispatchTouchEvent方法,进行事件的分发,actiivity就是上面2图中的viewgroup(不考虑顶层布局啥的),这个时候遍历它,activity中有个button的view。
    • 接着是button该view拿到该事件,然后执行dispatchTouchEvent进行分发。但是他自己是view,而不是viewgroup,没有子view
    • button就执行自己的onTouch事件(上面有说onTouch事件要早于onTouchEvent事件,若onTouch返回true,就不执行onTouchEvent)
    • 接着是执行onTouchEvent事件

ps:最后才会响应button的onClick事件方法。这里没有打印.

####在activity中的修改dispatchTouchEvent down中返回false

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("marc", TAG + "-dispatchTouchEvent-Action_DOWN");
            //这里返回false,就不会继续向下把down事件分到到子view,反言之,子view就不能够响应。
            return false;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            Log.i("marc", TAG + "-dispatchTouchEvent-Action_UP");
    }
    return super.dispatchTouchEvent(event);
}

log日志如下:

02-09 16:01:10.483 18564-18564/? I/marc: MVPActivity-dispatchTouchEvent-Action_DOWN
02-09 16:01:10.523 18564-18564/? I/marc: MVPActivity-dispatchTouchEvent-Action_UP
02-09 16:01:10.523 18564-18564/? I/marc: MVPActivity-onTouchEvent-Action_UP

可以看到在activity中的dispatchTouchEvent返回false后,就不进行下面的遍历分发了。直接执行接下来的Up事件的分发。这里up事件虽然没有返回false,但是没有down事件了。后续的move和up事件也就没了。所以直接还是执行activity的onTouchEvent事件

但是返回true跟返回false 日志一样。那是因为在当前事件中,reture了,就跳出当前switch了。就不执行下面的了

大家经常听过:在事件分发中,返回false表示不向下分发,返回true表示消费掉事件。这句话是针对ViewGroup说的。返回false不分发,返回true,消费掉也不分发了


###修改MyButton的dispatchTouchEvent的MotionEvent.ACTION_DOWN返回false

 @Override
public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Log.i("marc","My-Button-dispatchTouchEvent-Action_DOWN");
            return false;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            Log.i("marc","My-Button-dispatchTouchEvent-Action_UP");
    }
    return super.dispatchTouchEvent(event);
}

log日志打印如下:

02-09 16:26:34.633 29520-29520/? I/marc: MVPActivity-dispatchTouchEvent-Action_DOWN
02-09 16:26:34.643 29520-29520/? I/marc: My-Button-dispatchTouchEvent-Action_DOWN
02-09 16:26:34.653 29520-29520/? I/marc: MVPActivity-onTouchEvent-Action_DOWN
02-09 16:26:34.653 29520-29520/? I/marc: MVPActivity-dispatchTouchEvent-Action_UP
02-09 16:26:34.653 29520-29520/? I/marc: MVPActivity-onTouchEvent-Action_UP

可以看到到down事件分发到button的dispatchTouchEvent中时,返回了false,就不进行分发了。所以button的后续的move、up、onTouch、onTouchEvent就都不会响应。

###修改MyButton的dispatchTouchEvent的MotionEvent.ACTION_DOWN返回true

log日志如下:

02-09 16:30:05.053 31599-31599/? I/marc: MVPActivity-dispatchTouchEvent-Action_DOWN
02-09 16:30:05.053 31599-31599/? I/marc: My-Button-dispatchTouchEvent-Action_DOWN
02-09 16:30:05.083 31599-31599/? I/marc: MVPActivity-dispatchTouchEvent-Action_UP
02-09 16:30:05.083 31599-31599/? I/marc: My-Button-dispatchTouchEvent-Action_UP
02-09 16:30:05.083 31599-31599/? I/marc: MVPActivity-onTouch-Action_UP
02-09 16:30:05.083 31599-31599/? I/marc: My-Button-onTouchEvent-Action_UP

可以看到button的dispatchTouchEvent 中的down返回true消费了。但是其他的move和up事件还是会继续响应的。
同理:在up中返回false或者true是一样的道理。唯一注意的是。up事件是一系列动作的结束,不像down事件是一系列动作的开始。