Android 事件分发机制

来源:互联网 发布:淘宝专享价怎么设置 编辑:程序博客网 时间:2024/06/05 17:16

事件的重要处理方法

dispatchTouchEvent 事件分发
onInterceptTouchEvent 事件阻拦
onTouchEvent 事件处理

本文主要针对View的单点触控事件进行分析,不对多点触控和其他类型控件的Touch事件进行说明。
View的单点Touch事件一共有四种状态:
MotionEvent.ACTION_DOWN: 按下时
MotionEvent.ACTION_MOVE: 移动时
MotionEvent.ACTION_UP: 抬起时
MotionEvent.ACTION_CANCEL:取消时
要想了解Android中所有触控事件的信息,请查看官方文档:
http://developer.android.com/intl/zh-cn/reference/android/view/MotionEvent.html

View有关Touch事件一共有三种方法:
boolean dispatchTouchEvent(MotionEvent event):分配事件,控制下一步应该调用本View的哪个方法;
boolean onInterceptTouchEvent(MotionEvent event) :拦截事件,控制下一步应该调用本View的onTouchEvent,还是丢给下一级View
boolean onTouchEvent(MotionEvent event):处理事件,是否消耗掉此事件,还是传递给上一级

事件说明:
1.如果dispatchTouchEvent返回true,则交给此view的onTouchEvent处理,否则交给onInterceptTouchEvent处理;
2.如果onInterceptTouchEvent返回true,则交给onTouchEvent处理,否则传递给子view;
3.如果任何一层的onTouchEvent的down返回true,则表示此次event已消耗,后续事件都给此view处理,并且不会再调用此 view的onInterceptTouchEvent,当然上层的所有view还要调用dispatchTouchEvent和 onInterceptTouchEvent;
4.如果任何一层的onTouchEvent的down返回false,则表示马上调用上一级的onTouchEvent的down,直到遇到true或 最顶层消失;而且此view不再接收move和up事件,但是如果move和up时返回false则不会不再接收其他事件;
5.如果非最底层的onInterceptTouchEvent的move或up中返回true,那么会触发一次最底层view的 dispatchTouchEvent和onTouchEvent的cancel事件,和其他层子view的dispatchTouchEvent和 onInterceptTouchEvent的cancel事件,此时不会触发此view的onTouchEvent事件而是下次move或up事件再触 发,并且不会再触发所有子view的事件了;
6.所有要触发的View,如果始终没有拦截事件,那么最顶层的View一定会调用onTouchEvent方法;
7.down时,看坐标点有哪个view,则所有事件才会传递给相应的view,dispatchTouchEvent时就拦截了要忽略的view;
8.所有方法的cancel和onTouchEvent的move、up,可以忽略返回值;

下面是ACTION_DOWN事件的流程图:

这里写图片描述

Touch的Down事件流程图
补充一下老外总结的:
The best place to demystify this is the source code. The docs are woefully inadequate about explaining this.
dispatchTouchEvent is actually defined on Activity, View and ViewGroup. Think of it as a controller which decides how to route the touch events.
For example, the simplest case is that of View.dispatchTouchEvent which will route the touch event to either OnTouchListener.onTouchEvent if its defined or to the extension method onTouchEvent.
For ViewGroup.dispatchTouchEvent things are way more complicated. It needs to figure out which one of its child views should get the event (by calling child.dispatchTouchEvent). This is basically a hit testing algorithm where you figure out which child view’s bounding rectangle contains the touch point coordinates.
But before it can dispatch the event to the appropriate child view, the parent can spy and/or intercept the event all together. This is what onInterceptTouchEvent is there for. So it calls this method first before doing the hit testing and if the event was hijacked (by returning true from onInterceptTouchEvent) it sends a ACTION_CANCEL to the child views so they can abandon their touch event processing (from previous touch events) and from then onwards all touch events at the parent level are dispatched toonTouchListener.onTouch (if defined) or onTouchEvent(). Also in that case, onInterceptTouchEvent is never called again.
Would you even want to override [Activity|ViewGroup|View].dispatchTouchEvent? Unless you are doing some custom routing you probably should not.
The main extension methods are ViewGroup.onInterceptTouchEvent if you want to spy and/or intercept touch event at the parent level and View.onTouchListener/View.onTouchEvent for main event handling.
All in all its overly complicated design imo but android apis lean more towards flexibility than simplicity.

解决ViewPager嵌套ViewPager问题的解决办法

重要方法

getParent().requestDisallowInterceptTouchEvent(boolean bool)
设置为ture,表示请求父布局不要拦截Touch事件

设置为false,表示请求父布局可以拦截Touch事件

解决办法是:
自定义嵌套中的ViewPager的onTouchEvent方法,在其中通过以上方法通知父布局在必要时刻不要拦截事件。代码如下所示:

public class InnerViewPager extends ViewPager {    public InnerViewPager(Context context) {        super(context);    }    public InnerViewPager(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(MotionEvent motionEvent) {        // 当子控件中的数量小于等于1时,不拦截事件        if (getChildCount() <= 1) {            return super.onTouchEvent(motionEvent);        }        switch (motionEvent.getAction()) {        case MotionEvent.ACTION_DOWN:            getParent().requestDisallowInterceptTouchEvent(true);            break;        case MotionEvent.ACTION_MOVE:            getParent().requestDisallowInterceptTouchEvent(true);            break;        case MotionEvent.ACTION_UP:            getParent().requestDisallowInterceptTouchEvent(false);            break;        case MotionEvent.ACTION_CANCEL:            getParent().requestDisallowInterceptTouchEvent(false);            break;        default:            break;        }        return super.onTouchEvent(motionEvent);    }}