1. 程式人生 > >可能是最詳細的Android點選事件處理詳解(三)

可能是最詳細的Android點選事件處理詳解(三)

前兩篇文章:
可能是最詳細的Android點選事件處理詳解
可能是最詳細的Android點選事件處理詳解(二)
這裡再次延伸一下,在ScrollView和RecyclerView巢狀中touch事件的傳遞過程,以及巢狀滑動衝突的問題。

image

如上圖,外層是一個NestedScrollView,上半部分是一個400dp的RecyclerView。下面是用來填充的一些TextView。

本篇主要分四塊來講述:
1. 滑動上半部分的RecyclerView
2. 滑動下半部分的NestedScrollView
3. NestedScrollView內部只有RecyclerView滑動沒有Fling效果的情況
4. 處理上半部分RecyclerView滑動到底部,焦點在RecyclerView上滑動沒有Fling效果的情況

一,滑動上半部分的RecyclerView

日誌如下:
TouchScrollViewActivity[dispatchTouchEvent, 64]: ACTION_DOWN
NormalScrollView[dispatchTouchEvent, 35]: ACTION_DOWN
NormalScrollView[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalRecyclerView[dispatchTouchEvent, 36]: ACTION_DOWN
NormalRecyclerView[onInterceptTouchEvent, 63]: ACTION_DOWN
NormalView[dispatchTouchEvent, 84]: ACTION_DOWN
NormalView$2[onTouch, 57]: ACTION_DOWN
NormalView[onTouchEvent, 111]: ACTION_DOWN

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalRecyclerView[dispatchTouchEvent, 40]: ACTION_MOVE
NormalRecyclerView[onInterceptTouchEvent, 67]: ACTION_MOVE
NormalView[dispatchTouchEvent, 96]: ACTION_CANCEL
NormalView$2[onTouch, 68]: ACTION_CANCEL
NormalView[onTouchEvent, 122]: ACTION_CANCEL

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalRecyclerView[dispatchTouchEvent, 40]: ACTION_MOVE
NormalRecyclerView[onTouchEvent, 94]: ACTION_MOVE
TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalRecyclerView[dispatchTouchEvent, 40]: ACTION_MOVE
NormalRecyclerView[onTouchEvent, 94]: ACTION_MOVE

TouchScrollViewActivity[dispatchTouchEvent, 72]: ACTION_UP
NormalScrollView[dispatchTouchEvent, 42]: ACTION_UP
NormalRecyclerView[dispatchTouchEvent, 43]: ACTION_UP
NormalRecyclerView[onTouchEvent, 97]: ACTION_UP

首先說下一下只有400dp高度的RecyclerView在滑動的時候是有Fling效果的。因為高度的限定,焦點在RecyclerView內,而且RecyclerView是可以滑動的,此時雖然ScrollView也是可以滑動,但是優先處理內部滑動,並且也有Fling效果,不存在衝突問題。因為系統已經幫我們處理了。

從日誌看跟我們可能是最詳細的Android點選事件處理詳解(二)
中滑動日誌是一樣的。只不過又多了一層ScrollView而已。這裡不做過多描述,重點在於此時的RecyclerView有Fling效果。

二,滑動下半部分的NestedScrollView

日誌如下:
TouchScrollViewActivity[dispatchTouchEvent, 64]: ACTION_DOWN
NormalScrollView[dispatchTouchEvent, 35]: ACTION_DOWN
NormalScrollView[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalView[dispatchTouchEvent, 84]: ACTION_DOWN
NormalView$2[onTouch, 57]: ACTION_DOWN
NormalView[onTouchEvent, 111]: ACTION_DOWN

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalView[dispatchTouchEvent, 89]: ACTION_MOVE
NormalView$2[onTouch, 61]: ACTION_MOVE
NormalView[onTouchEvent, 115]: ACTION_MOVE

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalView[dispatchTouchEvent, 96]: ACTION_CANCEL
NormalView$2[onTouch, 68]: ACTION_CANCEL
NormalView[onTouchEvent, 122]: ACTION_CANCEL

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onTouchEvent, 92]: ACTION_MOVE

TouchScrollViewActivity[dispatchTouchEvent, 72]: ACTION_UP
NormalScrollView[dispatchTouchEvent, 42]: ACTION_UP
NormalScrollView[onTouchEvent, 95]: ACTION_UP

首先描述一下此時的手指焦點是在ScrollView下面的View上面的此時滑動的是ScrollView是有Fling效果的。

從日誌看跟我們可能是最詳細的Android點選事件處理詳解(二)
中滑動日誌是一樣的。只不過是將RecyclerView換成了ScrollView。這裡不做過多描述,重點在於此時的ScrollView有Fling效果。因為內部的View不具備滑動效果。不存在衝突。

三,NestedScrollView內部只有RecyclerView滑動沒有Fling效果的情況

日誌如下:

TouchScrollViewActivity[dispatchTouchEvent, 64]: ACTION_DOWN
NormalScrollView[dispatchTouchEvent, 35]: ACTION_DOWN
NormalScrollView[onInterceptTouchEvent, 61]: ACTION_DOWN
NormalRecyclerView[dispatchTouchEvent, 36]: ACTION_DOWN
NormalRecyclerView[onInterceptTouchEvent, 63]: ACTION_DOWN
NormalView[dispatchTouchEvent, 84]: ACTION_DOWN
NormalView$2[onTouch, 57]: ACTION_DOWN
NormalView[onTouchEvent, 111]: ACTION_DOWN

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalRecyclerView[dispatchTouchEvent, 40]: ACTION_MOVE
NormalRecyclerView[onInterceptTouchEvent, 67]: ACTION_MOVE
NormalView[dispatchTouchEvent, 96]: ACTION_CANCEL
NormalView$2[onTouch, 68]: ACTION_CANCEL
NormalView[onTouchEvent, 122]: ACTION_CANCEL

TouchScrollViewActivity[dispatchTouchEvent, 68]: ACTION_MOVE
NormalScrollView[dispatchTouchEvent, 39]: ACTION_MOVE
NormalScrollView[onInterceptTouchEvent, 65]: ACTION_MOVE
NormalRecyclerView[dispatchTouchEvent, 40]: ACTION_MOVE
NormalRecyclerView[onTouchEvent, 94]: ACTION_MOVE

TouchScrollViewActivity[dispatchTouchEvent, 72]: ACTION_UP
NormalScrollView[dispatchTouchEvent, 42]: ACTION_UP
NormalScrollView[onInterceptTouchEvent, 69]: ACTION_UP
NormalRecyclerView[dispatchTouchEvent, 43]: ACTION_UP
NormalRecyclerView[onTouchEvent, 97]: ACTION_UP

此時的滑動日誌其實和RecyclerView限制高度。滑動RecyclerView的日誌一樣,但是因為此時的ScrollView內部只有一個RecyclerView此時的ScollView和RecyclerView高度其實是一樣的。此時系統在滑動處理的時候,沒有幫助我們處理,此時滑動的時候從日誌看是作用到內部的RecyclerView了,但是此時的RecyclerView沒有Fling效果,即快速上滑螢幕,RecyclerView只會動一點,而不是慣性滾動很多。

對於這種情況其實很好解決,因為ScrollView和RecyclerView高度一樣。滑動內層和外層都是可以的。而一般處理衝突在外層處理比較方便一點。這裡又三種處理方式。
1. 在ScrollView層的onInterceptTouchEvent去攔截Move事件(這裡需要對一些細微move做一些處理,不然不容易出發touch到內層的事件),不讓內層的RecyclerView接收滑動事件,直接滑動ScrollView即可,此時在滑動就可以有Fling效果了。
2. recyclerView.setNestedScrollingEnabled(false);可以遮蔽RecyclerView的滑動,直接讓外層的ScrollView滑動。
3. 重寫LinearLayoutManager下的canScrollVertically()讓他返回false禁止豎向滑動。

以上三種方法均是遮蔽內層的滑動,讓外層進行滑動。

四,處理上半部分RecyclerView滑動到底部,焦點在RecyclerView上滑動沒有Fling效果的情況

首先我們知道,單獨滑動ScrollView和RecyclerView都是可以有Fling效果的,但是當RecyclerView和ScrollView同時滑動的時候,就不行了。這個時候我們要做的就是區分他們的滑動,因為系統以及幫我們處理了一部分。我們需要做的只是在臨界點去讓他們分別滑動。

提供如下思路:
1. 在外層ScrollView的onInterceptTouchEvent去攔截move事件
2. 當焦點在RecyclerView時,去判斷內層RecyclerView是否滑動到底部了。如果沒有滑動到底部,我們要讓它去滑動內層的RecyclerView。但是外層攔截了Move。我們需要呼叫getParent().requestDisallowInterceptTouchEvent(true);去駁回外層的Move攔截,讓他不去攔截Move。這個時候就可以滑動內層RecyclerView
3. 當RecyclerView滑動到底部的時候,這個時候如果焦點還在RecyclerView滑動就不會有Fling效果,我們呼叫getParent().requestDisallowInterceptTouchEvent(false);不去駁回外層攔截,讓他執行ScrollView的滑動。這個時候滑動ScrollView就有Fling效果了。

思路是這樣的。肯定可以。只不過有一些細節需要處理。程式碼後續我會補上。


專案原始碼: https://github.com/wangxp423/ViewExercise

在,首頁 -> Touch事件研究中。