前面介紹了OverScroll的使用,沒看過文章的同學可以先了解下《類似微信首頁彈性滾動和慣性滾動效果的實現——OverScroll》
接下來介紹OverScroll的實現原理。
CoordinatorLayout
CoordinatorLayout
是在Support 包中功能強大的佈局容器,它本質是一個 FrameLayout,然而它允許開發者通過自定義Behavior
協調各個子view,實現各種複雜酷炫的UI互動效果。
使用CoordinatorLayout
需要在 build.gradle 加入:
implementation 'com.android.support:design:26.1.0'
複製程式碼
網上很多關於CoordinatorLayout
的入門文章,這裡筆者不再贅述,所謂實踐大於理論,本文講述如何利用CoordinatorLayout+Behavior實現彈性滑動和慣性滑動,從側面去理解它的使用原理.
本文實現類似微信首頁的彈性滑動和慣性滑動效果,支援水平和垂直方向上的滾動,如下圖所示:
Behavior
CoordinatorLayout
主要是通過Behavior
來協調子view,這裡涉及到的Behavior
的關鍵方法如下:
方法 | 描述 |
---|---|
boolean onStartNestedScroll(CoordinatorLayout parent, View child, View directTargetChild, View target, int nestedScrollAxes, int type) | 根據返回值判斷是否要處理target 發生的滑動,一般用於判斷是否要處理某個方向上的滑動.例如 return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 表示處理垂直方向方向上的滑動.接下來的滑動事件將回撥給下面的方法處理. |
void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) | 當target 即將發生滑動時呼叫,在這裡可以做攔截處理.可以修改引數consumed 表示消耗(攔截)了多少畫素。例如target 控制元件本身想要垂直方向上滑動100px,而我們需要攔截掉80px,則要設定 consumed[1] = 80 ,(consumed[0] 、consumed[1] 分別對應x軸和y軸),最後target 控制元件實際只滑動了20px. |
void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) | target 控制元件發生滑動後呼叫, dyConsumed為實際消耗的距離,dyUnconsumed為未消耗的距離.例如上面的滑動,此時dyConsumed = 20 ,dxUnconsumed = 0 ,如果dyConsumed = 15 ,dxUnconsumed = 5 則表示target 在滑動15px距離時到達了邊界,我們可以利用dxUnconsumed 處理一些越界後的滑動. |
boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) | 使用者快速滑動target 並鬆開手指發生慣性滑動之前呼叫,返回true 表示攔截該慣性滑動事件. |
void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int type) | 所有的滑動停止後呼叫. |
原理分析
彈性滑動和慣性滑動都屬於過度滑動(Over scroll),即在達到正常滑動範圍的邊界後繼續滑動.因此,我們只需要處理在達到邊界時的越界滑動效果.關鍵的處理邏輯如下:
上圖描述了向下滑動過程中需要處理的關鍵邏輯,同理,向上滑動也採取類似的處理.
另外我們還要處理慣性滑動,當快速滑動產生fling事件時,讓列表滑到邊界時仍能夠慣性滑動一點距離.這裡需要藉助系統提供的工具類OverScroller
, 主要在onNestedPreFling
裡相關的慣性滑動的引數傳入OverScroller
.
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
if (child == target) {
mOverScroller.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
複製程式碼
後面便可以在滑動過程中通過mOverScroller.getCurrVelocity()
獲取當前時間慣性滑動的速度,當速度小於某個值時則停止滑動.
在Behavior
中的關鍵方法的引數中基本上最後都有個type值,文件解釋為the type of input which cause this scroll event
,即表示產生當前滑動的事件來源,當type == ViewCompat.TYPE_TOUCH
時表示由使用者觸控控制元件產生的滑動,type == ViewCompat.TYPE_NON_TOUCH
時表示由非觸控產生的滑動,比如慣性造成的滑動就是非觸控產生的.
因此我們在滑動過程中可以通過type判斷當前滑動是否為慣性滑動.
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
int type) {
if (type == ViewCompat.TYPE_TOUCH) { // scroll
} else { // fling
}
}
複製程式碼
最後,我們需要在停滑動時,如果列表發生了越界偏移,則需要把列表彈回原位,這裡通過ValueAnimator動畫實現即可.
程式碼實現
-
彈性滑動和慣性滑動過程中需要一些引數控制滑動效果,如最大的滑動距離,慣性滑動的最小速度,滑動的阻尼因子等,因此我們需要定義一個介面,和'Behavior`繫結的子View必須實現該介面,介面定義請檢視IOverScrollCallback,預設實現為SimpleOverScrollCallback.
-
自定義Behavior彈性滑動和慣性滑動,基類為BaseOverScrollBehavior,控制垂直滾動OverScrollVerticalBehavior,控制水平滾動OverScrollHorizontalBehavior.
-
讓NestedScrolling滑動控制元件(如
RecyclerView
,NestedScrollView
等)實現IOverScrollCallback
,提供相關滑動引數,這裡以OverScrollScrollView
控制元件為例,程式碼請檢視OverScrollScrollView.
效果(佈局相關:NestedScrollFragment ,layout_scrollview.xml):
專案地址OverScroll
多謝支援我的github專案>>>OverScroll!