《Android开发艺术探索》第三章事件分发机制小结

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = onInterceptTouchEvent(ev);
    if(consume){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

对于一个根ViewGroup来说,点击事件产生后,首先传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent返回true表示它要拦截当前事件,接着事件就交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用;如果返回false,则子元素的dispatchTouchEvent就会被调用。

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionEvent ev)

在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

public boolean onTouchEvent(MotionEvent ev)

在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次收到事件。


调用优先级:OnTouchListener > OnTouchEvent > OnClickListener

如果OnTouchListener中的OnTouch返回false,则OnTouchEvent被回调,否则不会。


事件产生后的传递顺序:Activity -> Window -> View


事件传递机制的结论:

1. 事件序列以down开始,经过一系列move,最终以up结束。
2. 一般一个事件序列只能被一个View拦截且消耗,但该View可以通过onTouchEvent返回false,而交给上级View处理。
3. 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理,并且它的onInterceptTouchEvent不再会被调用。
4. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一个事件序列中的其他事件都不会再交给他来处理,而是调用父元素的onTouchEvent。
5. 如果View只消耗了ACTION_DOWN,其他事件都没有消耗,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用。当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
6. ViewGroup默认不拦截任何事件,onInterceptTouchEvent返回false。
7. View没有onInterceptTouchEvent方法。
8.View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。
9.View的enable属性不影响onTouchEvent的默认返回值,该值只受clickable和longClickable影响。
10. onClick发生的前提是当前View可点击,并且收到了down和up事件。
11. 通过requestDisallowInterceptTouchEvent方法可以在自元素中干预父元素的事件分发,但是ACTION_DOWN除外。

//Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev){
    if(ev.getAction() == MotionEvent.ACTION_DOWN){
        onUserInteraction();
    }
    if(getWindow().superDispatchTouchEvent(ev)){//传递事件给decor view
        return true;
    }
    return onTouchEvent(ev);//由Activity自己处理
}

三个因素影响ViewGroup调用onInterceptTouchEvent:当前为ACTION_DOWN事件或者mFirstTouchTarget!=null, 并且FLAG_DISALLOW_INTERCEPT未设置,当前事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向子元素;

子View处理事件的逻辑:在子View中找到没有正在播动画,并且点击事件坐标落在自View内的,调用它的dispatchTouchEvent,如果返回true则mFirstTouchTarget被赋值,如果返回false则点击事件被ViewGroup发送给下一个子元素或者直接由ViewGroup的super.dispatchTouchEvent处理。

View的dispatchTouchEvent:首先判断是否有OnTouchListener且是enabled状态,则调用onTouch如果返回true则onTouchEvent不会被调用;

View的onTouchEvent:disabled状态的view只要clickable和longClickable有一个为true,依然会消耗事件。

如果View设置了TouchDelegate,则会调用代理的onTouchEvent方法,如果设置了onClick,也会调用


滑动冲突:

1. 外部拦截法:重写父容器的onInterceptTouchEvent,在ACTION_MOVE中根据滑动方向判断是否需要由父容器拦截;

2. 内部拦截法:父容器不拦截任何事件,如果子元素需要此事件就消耗掉,否则设置为让父元素消耗;

重写子元素的dispatchTouchEvent方法,在ACTION_DOWN中设置requestDisallowInterceptTouchEvent(true),在ACTION_MOVE中根据需要修改requestDisallowInterceptTouchEvent;重写父容器的onInterceptTouchEvent,ACTION_DOWN返回false,其他返回true。



你可能感兴趣的:(《Android开发艺术探索》第三章事件分发机制小结)