編輯:關於Android編程
上文我們徹底弄清楚了onTouch、onTouchEvent、onClick這三者的區別和聯系,也弄清楚Touch事件的傳遞原則以及事件在Activity、DecorView中的分發和傳遞。也給大家初步介紹了跟Touch事件分發息息相關的三個最重要的方法dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,並給大家留下了一個疑惑:Touch事件在ViewGroup和View中是怎麼分發和傳遞的? 那麼,本篇我們將為大家解決這個疑惑,重點介紹Touch事件在ViewGroup和View中的分發機制。
首先要帶大家探究的就是ViewGroup對Touch事件的分發過程,其主要實現是在ViewGroup的dispatchTouchEvent方法中實現的,該方法可以說是相當長、相當復雜。我們一段段來分析,我們先來看一下第一段源碼:
// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (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; }
當然也有一種特殊情況,那就是FLAG_DISALLOW_INTERCEPT這個標記位,它是由requestDisallowInterceptTouchEvent方法來設置的,一般是在子View中調用。一旦FLAG_DISALLOW_INTERCEPT設置後,ViewGroup將無法攔截除了DOWN事件外的其它事件。為什麼會是除了DOWN事件外的其它事件呢?因為如果是DOWN事件,那麼就會重置FLAG_DISALLOW_INTERCEPT標記位,從而使子View設置的該標記位失效。所以如果是DOWN事件的話,總會調用onInterceptTouchEvent方法來詢問是否攔截,這點從上述源碼也能看出。通過下面的這段源碼,我們能對ViewGroup處理DOWN事件有著更清晰的認識:
if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }如果是DOWN事件,會停止和清除所有的TouchTarget也就是說此時mFirstTouchTarget肯定為空,並調用resetTouchState方法對FLAG_DISALLOW_INTERCEPT進行重置。至此,我們對ViewGroup的dispatchTouchEvent方法基本上已經了解清楚了,當然這還是純粹從源碼的角度來分析的,我們還得寫個小demo來進行輔助驗證:同樣的,我們還是寫一個MainActivity,裡面有一個繼承LinearLayout的自定義ViewGroup——TestLayout,該ViewGroup中放一個自定義的子View——TestView。
/** * Created by leevi on 16/9/1. */ public class TestLayout extends LinearLayout{ private Context mContext; public TestLayout(Context context) { this(context,null); mContext = context; } public TestLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d("TestLayout", "dispatchTouchEvent!!!!!!"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("TestLayout", "onInterceptTouchEvent!!!!!!"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("TestLayout", "onTouchEvent!!!!!!"); return super.onTouchEvent(event); } }先只關注ViewGroup的情況,這種情況下,我們點擊TestView,能得到如下log:
很明顯可以看出能走到onInterceptTouchEvent方法,那我們再將dispatchTouchEvent方法的返回值改成true和false看看是什麼結果:
返回值改成true的情況:
很明顯可以看出,走了兩次dispatchTouchEvent方法,說明DOWN 和 UP事件都只走到ViewGroup的dispatchTouchEvent方法就不能往下走了,會父上傳遞給父控件的onTouchEvent方法(後面會聊到。)
那再來看看返回值是false的情況吧:
納尼!!!!!!!居然只打印了一次dispatchTouchEvent,這大大出乎我們的意料吧,意思是當我們DOWN事件到來後碰到這種情況整個Touch事件就結束了,後面的MOVE和UP事件都不會處理了。
所以我們可以得出結論,只有當ViewGroup的dispatchTouchEvent返回super.dispatchTouchEvent(event)的時候才能將事件傳遞下去。
那接下來我們就再以這個例子來探討一下ViewGroup的onInterceptTouchEvent方法。
public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true; } return false; }
當ViewGroup的onInterceptTouchEvent返回super或者false的時候,我們會得到以下log:
從以上log我們可以看出,事件由ViewGroup的dispatchTouchEvent方法傳遞給onInterceptTouchEvent方法,再傳遞給子View的dispatchTouchEvent方法,最後傳遞給子View的onTouchEvent方法來處理。
那如果我們將TestLayout的onInterceptTouchEvent方法的返回值改成true來攔截該事件呢?我們又會得到什麼情況呢?
我們可以發現,該事件不會傳遞到子View,並且會由onInterceptTouchEvent傳遞給onTouchEvent,由於沒有任何部分能處理DOWN事件,所以MOVE和UP事件也不復存在了。這就是我們看到上述log並沒有打印兩次的原因。
至此,Touch事件在ViewGroup中的傳遞我們基本上已經完全弄清楚了。那接下來我們看看當事件傳遞給子View後是怎麼分發和傳遞的呢?首先我們也是來研究一下View的dispatchTouchEvent方法。同樣還是從源碼入手:
public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
先將返回值改為true。
改成false的情況如下:
我們可以看出,跟ViewGroup的情況非常相似,無論是返回true還是false,事件都沒辦法傳遞到View的TouchEvent中來。而這裡我們會發現一個特別有意思的地方,當返回值為false的時候,事件會向上傳遞給父容器的onTouchEvent方法。這就是Touch事件的傳遞規則,先由外至內,如果內部不消化的話再由內傳遞至外。
我們還是可以得出一個結論,只有當dispatchTouchEvent返回值是super.dispatchTouchEvent(ev)時,Touch事件才能向下傳遞。
那最後我們再來看看View的onTouchEvent方法,默認返回值是false,代表不處理,不處理的話會傳遞給父控件的onTouchEvent方法。那我們如果將onTouchEvent的返回值改成true呢?
我們可以看到,onTouchEvent方法返回true就將該事件消費了,事件不會再由內向外傳遞。那我們就通過一張流程圖來總結一下Touch事件在ViewGroup和View中的分發和傳遞。
通過上圖,我們已經非常清楚地了解到了事件的分發機制。那最後我們就來了解一下,解決實際開發中沖突的兩類方法:
(1)、外部攔截法。所謂外部攔截法,就是Touch事件先傳遞給父容器,由父容器來決定攔截處理。如果父容器需要該事件,那就在onInterceptTouchEvent中返回true進行攔截,如果不需要該事件那就在onInterceptTouchEvent中返回false,將事件傳遞給子View。
(2)、內部攔截法。內部攔截法顧名思義就是父控件默認不攔截任何事件,所有事件全部傳遞給子View,如果子View需要事件則在onTouchEvent中返回true消費,否則返回false交給父容器處理。這裡需要配合requestDisallowInterceptTouchEvent方法使用,才能達到想要的效果。我們要重寫子View的dispatchTouchEvent方法,根據具體情況調用requestDisallowInterceptTouchEvent來對請求父控件對事件進行攔截和不攔截。這裡需要特別注意的是,父控件一定不能攔截DOWN事件,要不然所有事件都不會傳遞到子View了(前面已經驗證了),內部攔截法也起不了作用了。
至此,Touch事件分發機制基本上全部講完了,相信大家結合上下兩篇文章,對事件分發機制都有較為深入的了解了。後續還會帶來更多高質量的博客,如果大家有什麼需要深入了解的知識點,也可以再留言裡跟我說,我可以根據你們的留言寫相應的博客。
上一篇遺留了兩個問題。1、還是無法卸載;2、必須手動去點擊應用程序進入程序,再點擊按鈕,這顯得很麻煩。這一篇就解決上面兩個問題,做出最好的效果。首先解決無法卸載問題:在清
一、什麼是activityActivity 是用戶接口程序,原則上它會提供給用戶一個交互式的接口功能。它是 android 應用程序的基本功能單元。Activity 本身
Activity為我們提供一個可進行交互的窗口。當activity類創建了一個窗口,開發人員可以通過setContentView(View)接口把UI放到activity
““XXX(機主姓名)看這個,ht://********XXshenqi.apk”最近一種手機病毒爆發,機主收到這樣的短信,開頭是以發