這兩天的文章都是直接正如主題辖所,沒有什么開頭的描述的缘回,因為剛開始寫文章,也不知道說什么啦吧。啊拙寡,今天天氣不錯哈。
那么今天呢我講一下這個View的事件分發(fā)機制褒墨,這個很多面試的時候都會問到擎宝,因為這是View的一個核心點绍申,對View的點擊事件的一個分發(fā)的機制,所謂的事件分發(fā)機制呢胃碾,其實就是用戶對這個View進行觸碰產(chǎn)生的一個運動事件筋搏,比如說:點擊事件,長按事件這些俄周,這個事件產(chǎn)生了以后呢系統(tǒng)要把這個事件傳遞給一個View來進行消費髓迎,而這個傳遞的過程就是分發(fā)過程排龄。事件分發(fā)過程是由三個方法來完成的
public boolean dispatchTouchEvent(MotionEvent ev)
用來進行事件分發(fā),只要你觸碰了任何控件尺铣,都會調(diào)用這個方法,主要是判斷是當(dāng)前view消費還是往下級view傳遞。
public boolean onInterceptTouchEvent(MotionEvent ev)
這個方法用來判斷是否攔截這個事件澈灼,默認為false也就是不攔截侄非,需要進行攔截的時候在dispatchTouchEvent內(nèi)部調(diào)用逞怨。
public boolean onTouchEvent(MotionEvent event)
用來處理當(dāng)前事件福澡,返回結(jié)果表示是否消費,如果不消費除秀,則這個事件不會再接收到算利。
那么他們?nèi)齻€有什么不可描述的關(guān)系呢,首先我們要先知道一個點擊事件產(chǎn)生后的傳遞過程暂吉,當(dāng)一個事件產(chǎn)生一定會先傳遞給當(dāng)前Activity慕的,然后Activity再傳遞給window挤渔,window會傳遞給當(dāng)前頁面的頂級View,最后這個View接收到事件以后再按照事件分發(fā)機制去分發(fā)事件嫉父。這里有一種情況就是眼刃,如果一個View的onTouchEvent返回了false鸟整,那么他的父容器的onTouchEvent也會被調(diào)用,以此類推弟头,所有的View都不消費當(dāng)前事件的話,那么最終這個事件會傳遞給Activity疹娶,Activity的onTouchEvent會進行消費掉伦连。這里采用一個很好的例子,是從別的書籍上看到的额港,一個領(lǐng)導(dǎo)分配了一個很難的任務(wù)給你歧焦,然后你不會(onTouchEvent返回false)绢馍,那么怎么辦呢,只能交回給上級(上級的onTouchEvent被調(diào)用)猖任,那如果你的上級也搞不定呢瓷耙,那就交給上級的上級哺徊,到最后最頂級的上級那里,進行消費掉盈滴。這個例子是不是很貼近生活轿钠,哈哈我也是從別得地方看到的。
那么我講了這么半天大家也肯定還不是很懂症汹,那么我們從源碼入手進行分析背镇,打開你們的studio,找到Activity破婆,在Activity里面有個方法叫
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if(getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
通過上面的代碼我們可以看出來胸囱,Activity將時間交給附屬的Window進行分發(fā)烹笔,如果返回true的話,那么事件循環(huán)就結(jié)束了饰豺,如果返回false的話就
代表著事件沒人處理柬帕,那么最后會交給Activity的onTouchEvent處理陷寝。
那么我接著Window往下看其馏,Window是怎么處理這個事件的呢,我們看源碼知道了Window是個抽象類仔引,抽象類怎么處理呢褐奥?所以我們要找他Window的實現(xiàn)類PhoneWindow,我們找到PhoneWindow的dispatchEvent方法
@Override
public boolean superDispatchTrackballEvent(MotionEvent event) {
return mDecor.superDispatchTrackballEvent(event);
}
通過上面的代碼我們可以看到PhoneWindow把事件傳遞給了mDecor撬码,那么這個mDecor是什么呢,我繼續(xù)往下看
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
那么我們可以看到夫否,mDecor是DecorView凰慈,那么這個DecorView又是什么呢驼鹅,注意看上面的注釋森篷,翻譯過來就是,這是窗口的頂級視圖疾宏,包含窗口裝飾触创。也就是說,PhoneWindow把事件傳遞給了DecorView也就是頂級視圖岩馍,而DecorView是繼承FrameLayout蛀恩,而FrameLayout又是繼承的Viewgroup茂浮,也就是說,事件一定會傳遞到Viewgroup里面去顽馋,所以我們直接去看Viewgroup的dispatchTouchEvent是怎么處理的寸谜。
// Check for interception.
final booleanintercepted;
if(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null) {
final booleandisallowIntercept = (mGroupFlags&FLAG_DISALLOW_INTERCEPT) !=0;
if(!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);// restore action in case it was changed
}else{
intercepted =false;
}
}else{
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted =true;
}
Viewgroup的dispatchTouchEvent的代碼邏輯比較多熊痴,我們就分段說明聂宾,先看上面這段代碼,上面注釋Check for interception.表示的意思是檢查攔截巾陕,也就是這段代碼的邏輯是是否攔截處理這個事件惜论,然后我們從代碼中可以看出止喷,Viewgroup在兩種情況下會判斷是否攔截事件,那兩種呢乾巧,一個是MotionEvent.ACTION_DOWN也就是按下的時候沟于,還有一個是mFirstTouchTarget!=null的時候,那這個是什么情況呢展懈,這里我們可以從后面的代碼中可以看出來供璧,在后面的代碼中我們可以看出如果ViewGroup的子元素處理成功時睡毒,mFirstTouchTarget會被賦值并指向子元素,也就是當(dāng)Viewgroup不攔截事件交由子元素處理時供搀,mFirstTouchTarget 钠至!=null棕洋,反過來說掰盘,ViewGroup如果攔截了當(dāng)前事件的話赞季,那么(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null)這個判斷就會不成立返回false,導(dǎo)致ViewGroup中的onInterceptTouchEvent不會再被調(diào)用次绘,并且同一事件系列的其他事件也會默認交給他處理邮偎。
我們接著再看ViewGroup不攔截事件交由子元素處理的時候义黎,看源碼:
finalView[] children =mChildren;
for(inti = childrenCount -1; i >=0; i--) {
final intchildIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
finalView child =getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if(childWithAccessibilityFocus !=null) {
if(childWithAccessibilityFocus != child) {
continue;
}
if(!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if(newTouchTarget !=null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits|= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if(dispatchTransformedTouchEvent(ev,false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime= ev.getDownTime();
if(preorderedList !=null) {
// childIndex points into presorted list, find original index
for(intj =0; j < childrenCount; j++) {
if(children[childIndex] ==mChildren[j]) {
mLastTouchDownIndex= j;
break;
}
}
}else{
mLastTouchDownIndex= childIndex;
}
mLastTouchDownX= ev.getX();
mLastTouchDownY= ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if(preorderedList !=null) preorderedList.clear();
}
上面這段代碼呢艇拍,我們可以看出來在遍歷ViewGroup所有的子元素宠纯,然后通過子元素是否在播動畫和點擊事件的坐標(biāo)是否在子元素的區(qū)域內(nèi)來判斷子元素是否能夠接受這個事件。如果這兩個條件都滿足就交給這個子元素處理快集,我們看到dispatchTransformedTouchEvent這個方法廉白,點擊進去看蒙秒,你會發(fā)現(xiàn)
if(child ==null) {
handled =super.dispatchTouchEvent(event);
}else{
handled = child.dispatchTouchEvent(event);
}
他在判斷child也就是子元素是否為空晕讲,不為空的話就交給子元素處理了,從而完成了一輪事件分發(fā)弄息。如果子元素的dispatchTouchEvent返回true勤婚,這時我們暫時不考慮事件在子元素內(nèi)部是怎么處理的馒胆,那么mFirstTouchTarget就會被賦值同時跳出for循環(huán),看源碼
newTouchTarget= addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =true;
break;
這里完成了mFirstTouchTarget的賦值并終止對子元素的遍歷睦尽。吐過dispatchTouchEvent返回false型雳,ViewGroup就會把事件分發(fā)給下一個子元素(如果還有下一個子元素的話)当凡。
看到上面,你們一定會想mFirstTouchTarget是在哪里賦值的啊纠俭,我們看addTouchTarget這個方法
privateTouchTarget addTouchTarget(@NonNull View child,intpointerIdBits) {
finalTouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next=mFirstTouchTarget;
mFirstTouchTarget= target;
returntarget;
}
mFirstTouchTarget是一種單鏈表結(jié)構(gòu)沿量,mFirstTouchTarget是否被賦值直接影響到ViewGroup對事件的攔截,如果mFirstTouchTarget為空冤荆,那么ViewGroup將默認攔截同一序列中的所有點擊事件朴则。
今天就先講ViewGroup的事件分發(fā),如果大家覺得還是不理解的話匙赞,就去按照郭神的例子敲一下佛掖,這里附上一個連接:http://blog.csdn.net/guolin_blog/article/details/9153747
謝謝大家對我的支持妖碉。