View 的雜症之滑動衝突
當我們的頁面層級相對簡單的時候,一般都不會有滑動衝突的問題產生,但是隨著業務的擴充套件,我們的專案日益壯大,頁面也越來越複雜,當多個可滑動的控制元件巢狀在一起的時候,這時候就會產生令人摸不著頭髮的雜症-滑動衝突。
大致總結了一下,滑動衝突主要有以下幾種表現形式:
- 同一個滑動方向的滑動衝突
- 不同滑動方向的滑動衝突
- 亂來隨便搞的方向的滑動衝突
結合View的事件分發機制,針對滑動衝突,我們一般有兩種方法:外部攔截法和內部攔截法。
外部攔截法
顧名思義,通過外部攔截而達到解決滑動衝突的辦法,那這個外部攔截是在哪裡攔截呢?外部,指的就是父控制元件,攔截,那指的就是 onInterceptTouchEvent 這個方法,通過事件分發機制,我們知道,當onInterceptTouchEvent這個方法返回 true 的時候,事件就不再向下傳遞給子View 了,而是回撥該控制元件的 onTouchEvent 方法。利用這個特性,我們可以自己處理當前的事件需要交由誰來處理,如果是父控制元件,則直接在onInterceptTouchEvent返回 true 進行攔截就可以了。
內部攔截法
仍然顧名思義,外部不對事件進行攔截,而是交由子控制元件進行處理,如果子控制元件需要,則直接消耗該事件,否則交由父控制元件處理。這種方式往往還需要結合requestDisallowInterceptTouchEvent方法一起使用,這個方法在View的事件分發機制有提到過,它可以干擾父控制元件的攔截事件。這種方法相對複雜,它還需要父控制元件攔截除了ACTION_DOWN以外的事件,這樣,當子控制元件不需要處理的時候,才能交還由父控制元件處理。
下面談談針對上面提到的三種常見衝突我們需要如何處理。
同一個方向的滑動衝突
這種衝突常見的就是ScrollView 嵌套了 RecyclerView 這種形式。個人認為,同一方向的衝突用內部攔截法可能會相對好一些,假設當前觸控的子控制元件,則直接由子控制元件處理,如果非子控制元件,那全權由父控制元件處理,當然,這其中還需考量相應的實際業務進行編寫。當然了,如果僅僅是ScrollView 嵌套了 RecyclerView的衝突的話,其實可以用 Android 的原生控制元件 CoordinatorLayout,該控制元件已經很好的解決了這兩類控制元件巢狀一起的衝突。
不同方向的滑動衝突
這種衝突常見的應該就是頁面可上下滑動,可左右切換的形式。對於這樣的衝突,外部攔截法用起來相對更合適一些。假設當前的頁面是父控制元件是上下滾動,子控制元件的左右滑動,其實我們只需要在父控制元件的onInterceptTouchEvent方法中進行相應的業務邏輯判斷就可以了,當判斷為手勢為上下的時候,則父控制元件攔截事件進行處理,如果不是則放行。
亂來隨便搞的方向的滑動衝突
這種衝突都相對複雜了,往往都是頁面層級很深。說難也難,說簡單也簡單,難就難在多控制元件巢狀,處理邏輯比較麻煩,說簡單其實也不難,拆分開來,一層一層的解決也還好。
就說一下我當下遇到的一個問題吧。業務需求:頂層需監聽左右滑動做翻頁操作,次層需要可以上下滾動顯示,底層又有一個控制元件需要左右滑動。我 %¥¥@¥%&&…………%#@¥
好,現在對問題做一個拆分,我們可以把外層的左右和中間層的上下做一個分割槽,把中間層的上下和底層的左右做一個分割槽。那麼,我們只要分別處理這兩個地方的衝突就可以了。出於各種亂七八糟的考慮,我外層用的是外部攔截法,內層用的是內部攔截法。對於外層而言,我重寫了外層的onInterceptTouchEvent方法,根據回撥,拿到MotionEvent,進行業務判斷,如果是左右則交由當前控制元件處理,如果不是就放行。對於內層,因為不想處理這個中介軟體,所以採用了內部攔截法,在底層需要左右滑動的控制元件中重寫了其 dispatchOnTouchEvent方法,當判斷手勢是左右滑動的時候,則呼叫parent.requestDisallowInterceptTouchEvent(true)通知父控制元件我需要處理該事件,如果非左右滑動,則呼叫parent.requestDisallowInterceptTouchEvent(false)通知父控制元件自己處理該事件。
寫在後面
滑動衝突,確實是一個比較令人頭禿但又村頭不見村尾見的問題。但是隻要我們抓住解決衝突的要領:外部攔截法和內部攔截法,應該都可以解決大部分的衝突問題,如果仍舊是解決不了的,你可以懷疑懷疑你的產經了。:last_quarter_moon_with_face: