Android的事件分发机制

来源:互联网 发布:怎么js调用方法 编辑:程序博客网 时间:2024/05/17 07:01

点击事件的传递规则

同个事件:

是指手指触摸屏幕的那一刻起,到手指离开屏幕的那一刻结束,在整个过程中所产生的一系列事件,这个事件序列以 down 事件开始,中间含有数量不定的move事件,最终以 up 事件结束。

事件传递规则:

当一个点击事件发生之后,传递过程遵循如下顺序:Activity -> Window -> View

对于跟ViewGroup来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 方法就会被调用,如果这时ViewGroup的 onInterceptTouchEvent 方法返回 true 就表示它要拦截当前事件,接着事件就会交给ViewGroup处理,即它的 onTouchEvent 方法就会被调用;如果这个ViewGroup的 onInterceptTouchEvent 方法返回 false, 就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的ispatchTouchEvent 方法就会被调用,如此反复直到事件被最终处理。如果一个view的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法将会被调用,依此类推,如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。

伪代码:

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

事件分发过程的三个重要方法:

1 . public boolean dispatchTouchEvent(MotionEvent ev)

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

2 . public boolean onInterceptTouchEvent(MotionEvent ev)

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

ViewGroup中onInterceptTouchEvent方法默认返回false,即默认不拦截任何事件

View中没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。

3 . public boolean onTouchEvent(MotionEvent event)

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

View中的onTouchEvent默认返回true,消耗事件,除非它是不可点击的(clickable和longClickable都为false)。View的longClickable默认是false的,clickable则不一定,Button默认是true,而TextView默认是false。

ViewGroup不设置监听事件的话,onTouchEvent默认返回false,ViewGroup设置监听事件的话,onTouchEvent返回true,消耗事件。

View的enable属性不影响onTouchEvent的默认返回值。哪怕一个view是disable状态的,只要它的clickable或者longClickable有一个是true,那么它的onTouchEvent就会返回true。

某个view一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父容器去处理,即调用父容器的onTouchEvent方法;如果它消耗ACTION_DOWN事件,但是不消耗其他类型事件,那么这个点击事件会消失,此时父元素的onTouchEvent方法并不会被调用,当前view依然可以收到后续的事件(即当前view的onTouchEvent方法会被调用),但是这些事件最后都会传递给Activity处理。(因为正常情况下,一个事件序列只能被一个view拦截并消耗,因为一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了。)

其它:

1 . 如果给一个view设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前view的onTouchEvent方法会被调用;如果返回true,则onTouchEvent方法将不会被调用。在onTouchEvent方法中,如果当前view设置了OnClickListener,那么它的onClick方法会被调用,所以OnTouchListener的优先级比onTouchEvent高,OnClickListener的优先级最低。

2 . 事件传递过程总是先传递给父元素,然后再由父元素分发给子view,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外,即对于ACTION_DOWN时,ViewGroup会调用自己的onInterceptTouchEvent方法来询问是否要拦截事件。

VIew的滑动冲突

如何根据坐标得到滑动的方向:

根据滑动距离和水平方向形成的夹角;

根据水平和竖直方向滑动的距离差;

根据水平和竖直方向滑动的的速度差等

滑动冲突的解决方式:

1 . 外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。

父容器的onInterceptTouchEvent方法伪代码:

public boolean onInterceptTouchEvent(MotionEvent event) {    boolean intercepted = false;    int x = (int) event.getX();    int y = (int) event.getY();    switch (event.getAction()) {    case MotionEvent.ACTION_DOWN: {        intercepted = false;        break;    }    case MotionEvent.ACTION_MOVE: {        int deltaX = x - mLastXIntercept;        int deltaY = y - mLastYIntercept;        if (父容器需要当前点击事件) {            intercepted = true;        } else {            intercepted = false;        }        break;    }    case MotionEvent.ACTION_UP: {        intercepted = false;        break;    }    default:        break;    }    mLastXIntercept = x;    mLastYIntercept = y;    return intercepted;}

事件分发过程:

父容器的onInterceptTouchEvent方法,对于down事件和move父容器不需要当前点击得事件,返回false,才能传到子元素,因为如果返回true,一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了

如果父容器需要某类点击事件,返回true,拦截事件

对于up,返回false,因为在父容器不需要任何类点击事件时,如果在up事件返回true,会导致子元素无法接收到up事件;在父容器需要某类点击事件时,在up事件返回false,不会影响onTouchEvent方法接受到up事件(因为之前move事件,在父容器需要某类点击事件返回true,如果返回true,一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了)

2 . 内部拦截法:父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器来处理。这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。

子元素的dispatchTouchEvent方法伪代码:

public boolean dispatchTouchEvent(MotionEvent event) {    int x = (int) event.getX();    int y = (int) event.getY();    switch (event.getAction()) {    case MotionEvent.ACTION_DOWN: {]        Parent().requestDisallowInterceptTouchEvent(true);        break;    }    case MotionEvent.ACTION_MOVE: {        int deltaX = x - mLastX;        int deltaY = y - mLastY;        if (父容器需要此类点击事件) {            Parent().requestDisallowInterceptTouchEvent(false);        }        break;    }    case MotionEvent.ACTION_UP: {        break;    }    default:        break;    }    mLastX = x;    mLastY = y;    return super.dispatchTouchEvent(event);}

父容器的onInterceptTouchEvent方法伪代码

public boolean onInterceptTouchEvent(MotionEvent event){    int action = event.getAction();    if(action == MotionEvent.ACTION_DOWN){        return false;    }else{        return true;    }}

事件分发过程:

父容器的onInterceptTouchEvent方法,对于down,返回false,才能传到子元素,因为如果返回true,一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了

子元素的dispatchTouchEvent方法,对于dowm,调用Parent().requestDisallowInterceptTouchEvent(true),设置了FLAG_DISALLOW_INTERCEPT,父容器将无法拦截除了down以外的事件(即down以外的事件,父容器不会再调用onInterceptTouchEvent方法),所以down以后的其他事件传到子元素。当父容器需要某类点击事件时,调用Parent().requestDisallowInterceptTouchEvent(false),对FLAG_DISALLOW_INTERCEPT重置,父容器就可以继续调用它自己的onInterceptTouchEvent方法,所以在down以外的事件,父容器的onInterceptTouchEvent方法返回true,拦截事件。

其它:ViewGroup在事件分发时,如果是down事件就会重置FLAG_DISALLOW_INTERCEPT这个标志位,导致之前子元素设置的标志位无效,所以,如果我们想提前处理所以的点,要选择dispatchTouchEvent方法,前提事件能传递到当前的ViewGroup.

参考:Android开发艺术探索

1 0
原创粉丝点击