<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                版權信息 原文鏈接:[Android View的事件分發機制和滑動沖突解決](http://blog.csdn.net/qian520ao/article/details/77429593) --- ### 初探View事件 前言View的事件分發和滑動沖突處理是老生常談的知識了,因為最近擼了一個[仿QQ側滑刪除](https://github.com/qdxxxx/SwipeMenuContainer "optional title"),所以對該View事件有了更深入的總結。老鐵們是時候走一波[star](https://github.com/qdxxxx/SwipeMenuContainer "optional title")了。? 我們常說的View事件是指: 從手指親密接觸屏幕的那一刻到手指離開屏幕的這個過程,該事件序列以down事件為起點,move事件為過程,up事件為終點。? 一次down-move-up這一個事件過程我們稱為一個事件序列。所以我們今天研究的對象就是MotionEvent。 ### 事件分發 #### 理論知識 * public boolean dispatchTouchEvent(MotionEvent ev)? 用來分發事件,即事件序列的大門,如果事件傳遞到當前View的`onTouchEvent`或者是子View的`dispatchTouchEvent`,即該方法被調用了。? return true:?表示消耗了當前事件,有可能是當前View的`onTouchEvent`或者是子View的`dispatchTouchEvent`消費了,事件終止,不再傳遞。? return false:?調用父ViewGroup或則Activity的`onTouchEvent`。 (不再往下傳)。①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件都與該View無關,交由父類處理(父類的`onTouchEvent`方法)? return super.dispatherTouchEvent:?則繼續往下(子View)傳遞,或者是調用當前View的onTouchEvent方法; * public boolean onInterceptTouchEvent(MotionEvent ev)? 在`dispatchTouchEvent`內部調用,顧名思義就是判斷是否攔截某個事件。(注:ViewGroup才有的方法,View因為沒有子View了,所以不需要也沒有該方法)? return true:?ViewGroup將該事件攔截,交給自己的`onTouchEvent`處理。②而且這一個事件序列(當前和其它事件)都只能由該ViewGroup處理,并且不會再調用該`onInterceptTouchEvent`方法去詢問是否攔截。? return false:?繼續傳遞給子元素的`dispatchTouchEvent`處理。? return super.dispatherTouchEvent:?事件默認不會被攔截。 * public boolean onTouchEvent(MotionEvent ev)? 在`dispatchTouchEvent`內部調用? return true:?事件消費,當前事件終止。? return false:?交給父View的`onTouchEvent`。? return super.dispatherTouchEvent:?默認處理事件的邏輯和返回 false 時相同。 其實上面的關系可以用以下代碼簡單描述。 ~~~ public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false;//是否消費事件 if(onInterceptTouchEvent(ev)){//是否攔截事件 consume = onTouchEvent(ev);//攔截了,交給自己的View處理 }else{ consume = child.dispatchTouchEvent(ev);//不攔截,就交給子View處理 } return consume;//true:消費事件,終止。false:交給父onTouchEvent處理。并不再往下傳遞當前事件。 } ~~~ 有圖有真相 ![View事件分發](http://img.blog.csdn.net/20170820165410164?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 有點類似[責任鏈設計模式](http://blog.csdn.net/qian520ao/article/details/73558275 "optional title") #### 實戰講解 ##### 驗證View的事件分發 * 創建CustomViewGroup繼承FrameLayout * 創建CustomView繼承View xml ~~~ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="qdx.viewtouchevent.MainActivity"> //最外層為activity(白色背景) <qdx.viewtouchevent.CustomViewGroup android:layout_width="300dp" android:layout_height="400dp" android:layout_gravity="right" android:background="#84bf96"> //CustomViewGroup(綠色背景)包含CustomView(黃色背景) <qdx.viewtouchevent.CustomView android:layout_width="150dp" android:layout_height="300dp" android:layout_gravity="right" android:background="#f2eada" /> </qdx.viewtouchevent.CustomViewGroup> </FrameLayout> ~~~ ![無事件處理者](http://img.blog.csdn.net/20170820190556008?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 如上圖所示,down事件由activity->ViewGroup->View,因為View并沒有處理down事件,所以事件消費情況為false,并且最后由View->ViewGroup->activity傳遞。? ##### 驗證不消耗ACTION_DOWN事件 我們再來驗證①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件系列都與該View無關,交由父類處理(父類的`onTouchEvent`方法)? 根據上面文字描述,因為我們的`CustomViewGroup`和`CustomView`都沒有去處理任何事件,即當前序列的所有事件都return false,所以我們也無法接收/處理其他事件(move,up) ![ 不消耗ACTION_DOWN事件](http://img.blog.csdn.net/20170820192518176?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 我們再將customView設置為可點擊狀態,即消費touch事件。`setClickable(true);` ![子View消費touch事件](http://img.blog.csdn.net/20170820214522582?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ##### 驗證 ViewGroup事件攔截 viewGroup將事件攔截后,②而且這一個事件序列(當前和其它事件)都只能由該ViewGroup處理,并且不會再調用該`onInterceptTouchEvent`方法去詢問是否攔截。 ![View事件攔截](http://img.blog.csdn.net/20170820232415501?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 通過上面的幾個驗證,我們越來越接近真相,用通俗的話來解釋就是: > 老板發現BUG解決,一開始是由上級往下級問話。([類似責任鏈設計模式](http://blog.csdn.net/qian520ao/article/details/73558275 "optional title")) > > 例如突然間出現了BUG,老板問小組A有沒有空處理一下BUG(即分發ACTION_DOWN),小組A說沒時間(return false),那么老板就不會把這個序列的BUG(ACTION_MOVE和ACTION_UP)交給小組A。如果再次出現BUG,老板還會再次詢問小組A。① > > 如果你舉手攬了這個BUG(即攔截),那么這一事件的BUG都交由你解決,并且相同序列的BUG老板不會問話,直接找你處理。② #### 源碼分析ViewGroup 源碼分析這一塊主要還是基于《Android開發藝術探索》這本書的引導和理解做出的總結。PS:這本書性價比很高,涵蓋知識面廣。 ##### Activity的事件分發 Activity的事件分發還關系到View的繪制和加載機制,等待下一篇來更詳細認識這個知識點。 ~~~ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } //最終獲取到頂級View(ViewGroup)分發事件 //(getWindow().getDecorView().findViewById(android.R.id.Content)).getChildAt(0) if (getWindow().superDispatchTouchEvent(ev)) { return true; } //如果所有的View都沒有處理事件,則由Activity親自出馬 return onTouchEvent(ev); } ~~~ ##### ViewGroup的事件攔截 ~~~ public boolean dispatchTouchEvent(MotionEvent ev) { ...... final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); //清除FLAG_DISALLOW_INTERCEPT,并且設置mFirstTouchTarget為null resetTouchState(){ if(mFirstTouchTarget!=null){mFirstTouchTarget==null;} mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; ...... }; } final boolean intercepted;//ViewGroup是否攔截事件 //mFirstTouchTarget是ViewGroup中處理事件(return true)的子View //如果沒有子View處理則mFirstTouchTarget=null,ViewGroup自己處理 if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev);//onInterceptTouchEvent ev.setAction(action); } else { intercepted = false; //如果子類設置requestDisallowInterceptTouchEvent(true) //ViewGroup將無法攔截MotionEvent.ACTION_DOWN以外的事件 } } else { intercepted = true; //actionMasked != MotionEvent.ACTION_DOWN并且沒有子View處理事件,則將事件攔截 //并且不會再調用onInterceptTouchEvent詢問是否攔截 } ...... ...... } ~~~ 我們將上面的結論再次寫下來,方便對照。? ①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件都與該View無關,交由父類處理(父類的`onTouchEvent`方法)(dispatchTouchEvent)? ②而且這一個事件序列(當前和其它事件)都只能由該ViewGroup處理,并且不會再調用該`onInterceptTouchEvent`方法去詢問是否攔截。(onInterceptTouchEvent return true) * 首先我們分析上面第21行代碼: ViewGroup在兩種情況下會攔截事件(ACTION_DOWN || mFirstTouchTarget != null)所以反過來也就是說?I : 當ACTION_MOVE和ACTION_UP事件到來時,如果沒有子元素處理事件(mFirstTouchTarget==null),則ViewGroup的onInterceptTouchEvent不會再被調用,而且同一序列中的其它事件都會默認交給它處理(第34行 intercepted=true);與上面所說的①②呼應。 * 緊接著22行: ViewGroup`disallowIntercept`(不攔截)的判定是`FLAG_DISALLOW_INTERCEPT`標記位,這個標記是通過子View`requestDisallowInterceptTouchEvent`方法設置的。所以我們可以得出這么一個結論II : 當子View處理了ACTION_DOWN事件(mFirstTouchTarget =該子View),而且設置了FLAG_DISALLOW_INTERCEPT標記位,那么ViewGroup將無法攔截除了ACTION_DOWN以外的其它事件。(在11行代碼ACTION_DOWN時清除了FLAG_DISALLOW_INTERCEPT標記位,所以ViewGroup無論如何都可以選擇是否攔截處理ACTION_DOWN) 上面變著花樣的又一次驗證了①②個知識點,不得不說read the fuck source code讓我們可以找到一個處理滑動沖突的方法:子View處理DOWN事件并且設置`FLAG_DISALLOW_INTERCEPT`標記位,就可以不讓ViewGroup攔截DOWN以外的事件。 ##### ViewGroup的事件分發 ~~~ public boolean dispatchTouchEvent(MotionEvent ev) { final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); ...... if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); //如果子View沒有播放動畫,而且點擊事件的坐標在子View的區域內,繼續下面的判斷 continue; } //判斷是否有子View處理了事件 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { //如果已經有子View處理了事件,即mFirstTouchTarget!=null,終止循環。 newTouchTarget.pointerIdBits |= idBitsToAssign; break; } if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //點擊dispatchTransformedTouchEvent代碼發現其執行方法實際為 //return child.dispatchTouchEvent(event); (因為child!=null) //所以如果有子View處理了事件,我們就進行下一步:賦值 ...... newTouchTarget = addTouchTarget(child, idBitsToAssign); //addTouchTarget方法里完成了對mFirstTouchTarget的賦值 alreadyDispatchedToNewTouchTarget = true; break; } } } private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ...... if (child == null) { //如果沒有子View處理事件,就自己處理 handled = super.dispatchTouchEvent(event); } else { //有子View,調用子View的dispatchTouchEvent方法 handled = child.dispatchTouchEvent(event); ...... return handled; } ~~~ 上面為ViewGroup對事件的分發,主要有2點 1. 如果有子View,則調用子View的dispatchTouchEvent方法判斷是否處理了事件,如果處理了便賦值mFirstTouchTarget,賦值成功則跳出循環。 2. ViewGroup的事件分發最終還是調用View的`dispatchTouchEvent`方法,具體如上代碼所述。 至此View的事件分發機制已經演練完畢,如果事件分發機制理解深入的話,那么處理滑動沖突便是手到擒來了。 ### View的滑動沖突 關于View的滑動沖突我們就開門見山吧,因為上述的事件分發已經有足夠的理論知識了,我們可以單刀赴會了。 ![這里寫圖片描述](http://img.blog.csdn.net/20170822233424600?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ![View的滑動沖突](http://img.blog.csdn.net/20170822232321861?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 針對上圖,這個是比較普遍的滑動沖突事件,我們先拿它來開刀。 好記性不如爛筆頭,我們再次把結論搬到戰場上? ①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件都與該View無關,交由父類處理(父類的`onTouchEvent`方法)(dispatchTouchEvent)? ②而且這一個事件序列(當前和其它事件)都只能由該ViewGroup處理,并且不會再調用該`onInterceptTouchEvent`方法去詢問是否攔截。(onInterceptTouchEvent return true) I : 當ACTION_MOVE和ACTION_UP事件到來時,如果沒有子元素處理事件(mFirstTouchTarget==null),則ViewGroup的onInterceptTouchEvent不會再被調用,而且同一序列中的其它事件都會默認交給它處理(第34行 intercepted=true); #### 外部攔截 外部攔截顧名思義就是由父ViewGroup對事件攔截處理(所以重寫`onInterceptTouchEvent`方法即可),子View只能眼巴巴的處理父View“吃剩”的事件。主要有以下幾點。 * 父類不能攔截ACTION_DOWN,也就是說必須返回false,根據上述①②和?I?可得。 * 父類在ACTION_MOVE的時候根據需求,判斷是否攔截。 * ACTION_UP事件建議返回false或者`super.onInterceptTouchEvent`,因為如果已經攔截的話,那么并不會調用`onInterceptTouchEvent`方法再次詢問。如果不攔截,而且返回true,子View可能就無法觸發onClick等相關事件。 ViewGroup : 需要重寫`onInterceptTouchEvent`,判斷是否攔截即可。? 但是有一種情況:用戶正在水平滑動(事件已攔截給ViewGroup),但是水平滑動停止前用戶再進行豎直滑動,下面代碼我用`isSolve`進行簡單的處理。 ~~~ private boolean isIntercept; private boolean isSolve;//是否完成了攔截判斷,如果決定攔截,那么同系列事件就不能設置為不攔截 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mPointGapF.x = ev.getX(); mPointGapF.y = ev.getY(); return false;//down的時候攔截后,就只能交給自己處理了 case MotionEvent.ACTION_MOVE: if (!isSolve) {//是否已經決定攔截/不攔截? isIntercept = (Math.abs(ev.getX() - mPointGapF.x) > Math.abs(ev.getY() - mPointGapF.y)*2);//如果是左右滑動,且水平角度小于30°,就攔截 isSolve = true; } return isIntercept;//如果是左右滑動,就攔截 } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: scrollBy((int) (mPointGapF.x - ev.getX()), 0); mPointGapF.x = ev.getX(); mPointGapF.y = ev.getY(); break; } return super.onTouchEvent(ev); } ~~~ 子View : 和子View沒有多大關系,只需要處理自身的移動操作即可。 ~~~ public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mPointGapF.x = ev.getX(); mPointGapF.y = ev.getY(); break; case MotionEvent.ACTION_MOVE: scrollBy(0, (int) (mPointGapF.y - ev.getY())); mPointGapF.x = ev.getX(); mPointGapF.y = ev.getY(); break; } return true; } ~~~ ![外部攔截沖突](http://img.blog.csdn.net/20170823135102485?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ![外部攔截沖突](http://img.blog.csdn.net/20170823134034862?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlhbjUyMGFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) #### 內部攔截 II : 當子View處理了ACTION_DOWN事件(mFirstTouchTarget =該子View),而且設置了FLAG_DISALLOW_INTERCEPT標記位,那么ViewGroup將無法攔截除了ACTION_DOWN以外的其它事件。 ViewGroup : 只需在`onInterceptTouchEvent`MotionEvent.ACTION_DOWN時候不攔截,其他時候都需要攔截,否則父類的`onTouchEvent`就不能處理任何事件了。 ~~~ public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return false;//down的時候攔截后,就只能交給自己處理了 } return true;//如果不攔截,父類的onTouchEvent方法就無事件可以處理。 } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: scrollBy((int) (mPointGapF.x - ev.getX()), 0); mPointGapF.x = ev.getX(); mPointGapF.y = ev.getY(); break; } return super.onTouchEvent(ev); } ~~~ 子View : 需要在ACTION_DOWN事件設置getParent().requestDisallowInterceptTouchEvent(true),并且在ACTION_MOVE的時候通過判斷是否禁止父類的攔截。 ~~~ private boolean isSolve; private boolean isIntercept; @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: isIntercept = false; isSolve = false; mPointGapF.x = ev.getX(); mPointGapF.y = ev.getY(); getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: if (!isSolve) { isSolve = true; isIntercept = (Math.abs(ev.getX() - mPointGapF.x) < Math.abs(ev.getY() - mPointGapF.y) * 2); getParent().requestDisallowInterceptTouchEvent(isIntercept); } break; } return super.dispatchTouchEvent(ev); } ~~~ 最終的效果圖和外部攔截的效果一致,這里就不再次貼出來了。? ### 總結 通過理論和實戰,更清晰的了解了事件的分發機制,從而這些理論知識使得我們更有效的處理滑動沖突事件,所以以后只要再遇見滑動沖突事件,再次鞏固View的事件分發,萬變不離其宗,定能手到擒來解決這一問題!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看