android中view手勢滑動衝突的解決方法,主要解決方法有兩種,外部和內部攔截。有需要的可以參考下。
Android手勢事件的衝突跟點擊事件的分發過程息息相關,由三個重要的方法來共同完成,分別是:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
這個方法用來進行事件的分發。如果事件傳遞到view,那麼這個方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。
在上述方法內部調用,用來判斷是攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列當中,此方法不會被再次調用,返回結果表示是否攔截當前事件。
在dispathcTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接到事件。
例:
手勢衝突的解決方法就是用上面的三個方法;主要分爲兩種解決方法:·1外部攔截法 2內部攔截法
1.常見的滑動衝突場景
1.1 外部滑動方向和內部滑動的方向不一致
這種情況我們經常遇見,比如使用viewpaper+listview時,在這種效果中,可以通過左右滑動切換頁面,而每一個頁面往往又是一個listview,本來在這種情況下是有衝突的,但是Viewpaper內部處理了這個滑動衝突,因此採用viewpaper我們無需關注這個問題,如果我們採用的不是Viewpaper而是ScrollView等,那麼必須手動處理滑動衝突,否則內外兩層只能有一層滑動,那就是滑動衝突。另外內部左右滑動,外部上下滑動也同樣屬於該類。
1.2 外部滑動方向和內部滑動方向一致
這種情況就比較複雜,當內外兩層都在同一個方向可以滑動的時候,顯然存在邏輯問題,因爲當手指開始滑動的時候,系統無法知道用戶到底是想讓那一層動,所以當手指滑動的時候就會出現問題,要麼只能一層動,要麼內外兩成動的都很卡頓。
2.給出解決方案
2.1 外部攔截法
針對場景1,我們可以發現外部和內部的滑動方向不一樣也就是說只要判斷當前dy和dx的大小,如果dy>dx,那麼當前就是豎直滑動,否則就是水平滑動。明確了這個我就就可以根據當前的手勢開始攔截了。
從上一節中我們分析了view的事件分發,我們知道點擊事件的分發順序是 通過父佈局分發,如果父佈局沒有攔截,即onInterceptTouchEvent返回false,纔會傳遞給子View。所以我們就可以利用onInterceptTouchEvent()這個方法來進行事件的攔截。來看一下代碼:
上面的代碼差多就是外部攔截的通用模板了,在onInterceptTouchEvent方法中,
首先是ACTION_DOWN這個事件,父容器必須返回false,即不攔截事件,因爲一旦父容器攔截了ACTION_DOWN這個事件,那麼後續的ACTION_MOVE和ACTION_UP事件將直接交給父容器處理,這個時候事件沒法繼續傳遞給子元素了;
然後是ACTION_MOVE這個事件,這個事件可以根據需要決定是否攔截,如果父容器需要攔截就返回true,否則返回false;
最後是ACTION_UP這個事件,這裏必須返回false,因爲這個事件本身也沒有太多意義。
下面我們來具體做一下攔截的操作,我們需要在水平滑動的時候父容器攔截事件。
從上面的代碼來看,我們只是修改了一下攔截條件而已,所以說外部攔截還是很簡單方便的。在滑動的過程中,當水平方向的距離大時就判定水平滑動。
還是一貫我們做實驗來證明理論的風格,我們來自定義一個HorizontalScrollView來體現一下用外部攔截法解決衝突的快感。
先上一下代碼:
再來看一下佈局文件
以上就是外部處理滑動衝突的代碼,認真看一下,思路還是很清晰的。裏面還涉及了一些自定義View的知識,我會在後面的博文中認真分析一下代碼,你先看一下onInterceptTouchEvent處理滑動衝突的部分。
看一下效果圖哈。
2.2 內部攔截法
內部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器去處理,這種方法和Android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,這個方法的大體解釋就是:
requestDisallowInterceptTouchEvent是ViewGroup類中的一個公用方法,參數是一個boolean值,官方介紹如下
Called when a child does not want this parent and its ancestors to intercept touch events with ViewGroup.onInterceptTouchEvent(MotionEvent).
This parent should pass this call onto its parents. This parent must obey this request for the duration of the touch (that is, only clear the flag after this parent has received an up or a cancel.
android系統中,一次點擊事件是從父view傳遞到子view中,每一層的view可以決定是否攔截並處理點擊事件或者傳遞到下一層,如果子view不處理點擊事件,則該事件會傳遞會父view,由父view去決定是否處理該點擊事件。在子view可以通過設置此方法去告訴父view不要攔截並處理點擊事件,父view應該接受這個請求直到此次點擊事件結束。
使用起來外部攔截事件略顯複雜一點。下面我也先來看一下它的通用模板(注意下面的代碼是定義在子View中的):