Android View的点击事件传递

Android View的点击事件传递

View的定义

view是Android中所有组件的父类,不管是Button或者TextView这样的初学者所了解的简单的View,还是ViewGroup,LinearLayout等,都是View的子类。

点击事件

点击事件就是MotionEvent,即手指接触屏幕之后产生的一系列事件,一般需要关注的事件类型是:

ACTION_DOWN

就是手指刚刚接触屏幕的瞬间产生的动作,但是很多时候这个动作一般不会让某一个View来处理。

ACTION_MOVE

手指在屏幕上滑动,并且滑动的距离超过某个最小值(TouchSlop),才被判定为滑动,其中TouchSlop在不同设备上的值往往是不一样的,我们可以通过ViewConfiguration.get(mContext).getScaledTouchSlop()获取这个常量。

ACTION_UP

手指离开屏幕的产生的动作 。
通过以上我们可以得出,手指在屏幕上的一次操作,会产生出一系列的点击事件,比如简单的点击就会产生一个ACTION_DOWN和一个ACTION_UP,如果滑动的话,还需要在其中加入多个ACTION_MOVE 。

View的事件分发机制

View中点击事件的传递规则

View针对MotionEvent有三个处理函数:

public boolean dispatchTouchEvent(MotionEvent ev)

用来对点击事件进行分发,是View最早接收到MotionEvent 的函数,返回值代表是否消耗当前事件。

public boolean onInterceptTouchEvent(MotionEvent  ev)

在dispatchTouchEvent(ev)中调用,表示是否截断当前接受到的事件。

public boolean onTouchEvent(MotionEvent  ev)

在dispatchTouchEvent(ev)中调用,用来处理点击事件,结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,该View无法再次接收到事件。其中TRUE表示消耗当前事件。
上面三个方法的关系可以用以下代码表示:

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

从上面的代码我们可以看出,View的点击事件的传递规则:当上级View将点击事件传递搭配当前View的时候,View的dispatchTouchEvent就会被首先调用,然后如果当前View的onInterceptTouchEvent()返回值为TRUE,那么就会该事件传递给onTouchEvent处理,不然就将其交给下一级View的dispatchTouchEvent处理。同时,值得注意的是,每一个MotionEvent只表示一个ACTION_MOVE ,ACTION_UP或者ACTION_DOWN,我们可以通过以下方法得到该MotionEvent所代表的事件:

//ev为MotionEvent对象
ev.getAction()

同时这里有一个疑问,加入当前View的onInterceptTouchEvent()返回值为TRUE,但是onTouchEvent()的返回值为FALSE(当前没有消耗这个事件),那么这个MotionEvent到底去哪里了呢? 这种情况下这个MotionEvent会传递给上级View处理。
上述所说的只是大致的流程,其实还有很多细节的地方,有兴趣的人可以去看看Android源码。

其他总结

(一)同一序列事件是指从手指触摸屏幕开始,直到手指离开屏幕的时候,所产生的点击事件。

(二)在整个事件传递的过程中,Activity拿到事件对象,Activity把事件对象传递给PhoneWindow,PhoneWindow再传递给DecorView,DecorView通过遍历再传递到我们的ViewGroup。

(三)如果在当前View中设置了onTouchlistener,或者onClickListener,那么在整个View的点击事件的传递规则优先级为:onTouchlistener.onTouch()>onTouchEvent()>onClickListener.onClick()。如果想要截断这个事件可以在某一处将其返回值设置为TRUE。

(四)正常情况下,一个事件序列只能被一个Visw拦截且消耗。这一条的原因可以参考(五),因为一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个Vew将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

(五)某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceprTouchEvent不会再被调用。这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。

(六)某个View一旦开始处理事件,如果它不消耗ACTON_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事情交给这个程序员做了,二者是类似的道理。

(七)如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。

(八)ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false

(九)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。所以如果我们要对某个事件进行截断的时候,我们可以在它的onTouchEvent中进行处理,为了实现很多自定义的效果,我们必须得常常去对一些控件或者布局进行继承,并且重写其中的方法。

(十)view的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView 的clickable属性默认为false

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

(十二)onclick会发生的前提实际当前的View是可点击的,并且他收到了down和up的事件。

如果有任何疑问可以去看看
https://blog.csdn.net/huachao1001/article/details/51766225
写的真的很容易理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章