Android NestedScrolling解決滑動衝突問題(2) - fling問題與NestedScroll++
前一篇文章中分析瞭解決滑動衝突問題的NestedScroll 介面,也給出瞭解決此類問題的一般性方案:
NestedScrollingChild側
NestedScrollingChild
(後面簡稱NC)處理MotionEvent
(一般在onTouchEvent
中,如果是ViewGroup
還要注意onInterceptTouchEvent
的處理,攔截滑動相關的MotionEvent
事件),分析使用者滑動操作。
在滑動開始時,呼叫startNestedScroll
找到聯動此次滑動的NestedScrollingParent
(後面簡稱NP)。
對於每次使用者互動產生的滑動距離,先呼叫dispatchNestedPreScroll
,詢問聯動NP是否預先處理此滑動,如果NP預先處理了,會給出消耗掉的滑動距離。
對於NP預處理剩下的滑動距離,NC決定自己是否處理部分或者全部距離(自己的滑動)。
如果NC自己滾動之後,還剩下部分滑動距離,則呼叫dispatchNestedScroll
讓NP自行選擇是否處理最後剩下的這些滑動距離。
使用者互動停止滑動,呼叫stopNestedScroll
通知NC停止滑動聯動。
NestedScrollingParent側
在onStartNestedScroll
中,決定是否與此次NC發起的滑動請求聯動,如果決定聯動,返回true
,否則返回false
。返回true
之後,會收到onNestedScrollAccepted
回撥,表示NC同意與其聯動,可以開始做初始化操作了;返回false之後,後面的NC聯動操作不會通知此NestedScrollingParent
(不會收到後續的onNestedPreScroll
、onNestedScroll
、onStopNestedScroll
等)。
在onNestedPreScroll
中,決定是否預處理滑動單步,並給出消耗掉的滑動距離(不處理則為0)。
在onNestedScroll
中,決定是否消耗NC處理剩下的滑動距離。
在onStopNestedScroll
做聯動滑動收尾工作。
通過NC與NP的配合,可以做到很多複雜的滑動操作。只要分析了介面上外層檢視與內層檢視在滑動時的互動邏輯,就可以利用這兩個介面實現。
fling的處理
相對於滑動操作,還有一個fling操作,也叫猛劃,指使用者拖住UI元素快速滑動之後抬手,這時會有一個fling事件,一般的操作邏輯是UI元素在抬手之後按照初始速度做減速運動。
NestedScroll介面也提供了API處理fling事件,在NestedScrollingChild
中dispatchNestedPreFling
通知NP預處理fling事件,dispatchNestedFling
通知NP後處理fling事件。
boolean dispatchNestedPreFling(float velocityX, float velocityY); boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed); 複製程式碼
NestedScrollingParent
中對應的介面為onNestedPreFling
、onNestedFling
。
boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY); boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed); 複製程式碼
通過這幾個介面,可以讓NC和NP各自對fling事件做出反應,但是不能像滑動事件一樣聯動。即不能先讓NP預處理部分 fling 速度,然後NC處理剩下的部分 fling速度,再將最後剩下的交給NP繼續處理。這種情況下,上層UI元素與下層UI元素缺乏互動,很難做到像滑動操作一樣的UI效果(例如fling時先收起上層檢視部分內容,再滑動下層檢視)。
NestedScroll++
為了解決此問題,在support包26.0.0-beta2
版本中引入了NestedScroll
介面的升級版本(後面稱為NestedScroll++
):NestedScrollingParent2
、NestedScrollingChild2
。
在NestedScroll++
介面中,引入了touchtype
的概念:對於使用者手指觸控拖拽產生的滑動事件type為ViewCompat.TYPE_TOUCH
, fling產生的滑動事件type
為ViewCompat.TYPE_NON_TOUCH
。滑動介面的相應API中加入了此type
引數,如onNestedScroll
介面改為:
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type); 複製程式碼
NestedScroll++介面中,對於滑動事件的處理,與NestedScroll 介面一樣,只是API中加入了type 引數。
而對於fling事件的處理,不再依賴於dispatchNestedPreFling
、dispatchNestedFling
、onNestedPreFling
、onNestedFling
等介面,而是選擇使用與滑動事件相同的處理方式,只是type
不同(為ViewCompat.TYPE_NON_TOUCH
)。
相應的互動邏輯改為:
NestedScrollingChild側
在fling開始時,呼叫startNestedScroll
找到聯動此次滑動的NestedScrollingParent
(後面簡稱NP)。
每次重新整理檢視時,計算當前時間片由fling產生的滑動距離,先呼叫dispatchNestedPreScroll
,詢問聯動NP是否預先處理此滑動距離,如果NP預先處理了,會給出消耗掉的滑動距離。
對於NP預處理剩下的滑動距離,NC決定自己是否處理部分或者全部距離(自己的滑動)。
如果NC自己滾動之後,還剩下部分滑動距離,則呼叫dispatchNestedScroll
讓NP自行選擇是否處理最後剩下的這些滑動距離。
使用者互動停止滑動,呼叫stopNestedScroll通知NC停止滑動聯動。
NestedScrollingParent側
在onStartNestedScroll
中,決定是否與此次NC發起的fling聯動請求,如果決定聯動,返回true
,否則返回false
。返回true
之後,會收到onNestedScrollAccepted
回掉,表示NC同意與其聯動,可以開始做初始化操作了;返回false之後,後面的NC聯動操作不會通知此NestedScrollingParent
(不會收到後續的onNestedPreScroll
、onNestedScroll
、onStopNestedScroll
等)。
在onNestedPreScroll
中,決定是否預處理fling產生的滑動距離,並給出消耗掉的滑動距離(不處理則為0)。
在onNestedScroll
中,決定是否消耗NC處理剩下的滑動距離。
在onStopNestedScroll
做聯動滑動收尾工作。