來源 http://segmentfault.com/a/1190000002873657
編輯推薦:稀土掘金,這是一個針對技術開發者的一個應用,你可以在掘金上擷取最新最優質的技術幹貨,不僅僅是Android知識、前端、後端以至于産品和設計都有涉獵,想成為全棧工程師的朋友不要錯過!
轉載這篇文章是因為的文檔太少,雖然本文也是輕描淡寫,但是也是最詳細的了。不過文檔少也證明了
NestedScrolling
的功能很容易用其他方法替代,是以大家看看就好,不必太細究。
NestedScrolling
Android 在釋出
Lollipop
版本之後,為了更好的使用者體驗,Google為Android的滑動機制提供了
NestedScrolling
特性
NestedScrolling
的特性可以展現在哪裡呢?
比如你使用了
Toolbar
,下面一個
ScrollView
,向上滾動隐藏
Toolbar
,向下滾動顯示
Toolbar
,這裡在邏輯上就是一個
NestedScrolling
—— 因為你在滾動整個
Toolbar
在内的View的過程中,又
嵌套
滾動了裡面的
ScrollView
。

效果如上圖【别嫌棄我】
在這之前,我們知道
Android
對
Touch
事件的分發是有自己一套機制的。主要是有是三個函數:
dispatchTouchEvent
、
onInterceptTouchEvent
和
onTouchEvent
。
這種分發機制有一個漏洞:
如果子view獲得處理touch事件機會的時候,父view就再也沒有機會去處理這個touch事件了,直到下一次手指再按下。
也就是說,我們在滑動子View的時候,如果子View對這個滑動事件不想要處理的時候,隻能抛棄這個touch事件,而不會把這些傳給父view去處理。
注:本站這種觀點值得商量。
但是Google新的
NestedScrolling
機制就很好的解決了這個問題。
我們看看如何實作這個
NestedScrolling
,首先有幾個類(接口)我們需要關注一下
NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper
以上四個類都在
support-v4
包中提供,Lollipop的View預設實作了幾種方法。
實作接口很簡單,這邊我暫時用到了
NestedScrollingChild
系列的方法(因為Parent是support-design提供的
CoordinatorLayout
)
- @Override
- public void setNestedScrollingEnabled(boolean enabled) {
- super.setNestedScrollingEnabled(enabled);
- mChildHelper.setNestedScrollingEnabled(enabled);
- }
- @Override
- public boolean isNestedScrollingEnabled() {
- return mChildHelper.isNestedScrollingEnabled();
- }
- @Override
- public boolean startNestedScroll(int axes) {
- return mChildHelper.startNestedScroll(axes);
- }
- @Override
- public void stopNestedScroll() {
- mChildHelper.stopNestedScroll();
- }
- @Override
- public boolean hasNestedScrollingParent() {
- return mChildHelper.hasNestedScrollingParent();
- }
- @Override
- public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
- return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
- }
- @Override
- public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
- return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
- }
- @Override
- public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
- return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
- }
- @Override
- public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
- return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
- }
對,簡單的話你就這麼實作就好了。
這些接口都是我們在需要的時候自己調用的。childHelper幹了些什麼事呢?,看一下
startNestedScroll
方法
- /**
- * Start a new nested scroll for this view.
- *
- * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
- * method/{@link NestedScrollingChild} interface method with the same signature to implement
- * the standard policy.</p>
- *
- * @param axes Supported nested scroll axes.
- * See {@link NestedScrollingChild#startNestedScroll(int)}.
- * @return true if a cooperating parent view was found and nested scrolling started successfully
- */
- public boolean startNestedScroll(int axes) {
- if (hasNestedScrollingParent()) {
- // Already in progress
- return true;
- }
- if (isNestedScrollingEnabled()) {
- ViewParent p = mView.getParent();
- View child = mView;
- while (p != null) {
- if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
- mNestedScrollingParent = p;
- ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
- return true;
- }
- if (p instanceof View) {
- child = (View) p;
- }
- p = p.getParent();
- }
- }
- return false;
- }
可以看到這裡是幫你實作一些跟
NestedScrollingParent
互動的一些方法。
ViewParentCompat
是一個和父view互動的相容類,它會判斷api version,如果在Lollipop以上,就是用view自帶的方法,否則判斷是否實作了
NestedScrollingParent
接口,去調用接口的方法。
那麼具體我們怎麼使用這一套機制呢?比如子View這時候我需要通知父view告訴它我有一個嵌套的touch事件需要我們共同處理。那麼針對一個隻包含scroll互動,它整個工作流是這樣的:
一、startNestedScroll
首先子view需要開啟整個流程(内部主要是找到合适的能接受nestedScroll的parent),通知父View,我要和你配合處理TouchEvent
二、dispatchNestedPreScroll
在子View的
onInterceptTouchEvent
或者
onTouch
中(一般在 MontionEvent.ACTION_MOVE事件裡),調用該方法通知父View滑動的距離。該方法的第三第四個參數傳回父view消費掉的 scroll長度和子View的窗體偏移量。如果這個scroll沒有被消費完,則子view進行處理剩下的一些距離,由于窗體進行了移動,如果你記錄了手指最後的位置,需要根據第四個參數
offsetInWindow
計算偏移量,才能保證下一次的touch事件的計算是正确的。
如果父view接受了它的滾動參數,進行了部分消費,則這個函數傳回true,否則為false。
這個函數一般在子view處理scroll前調用。
三、dispatchNestedScroll
向父view彙報滾動情況,包括子view消費的部分和子view沒有消費的部分。
如果父view接受了它的滾動參數,進行了部分消費,則這個函數傳回true,否則為false。
這個函數一般在子view處理scroll後調用。
四、stopNestedScroll
結束整個流程。
整個對應流程是這樣
子view | 父view |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
一般是子view發起調用,父view接受回調。
我們最需要關注的是
dispatchNestedPreScroll
中的
consumed
參數。
- public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) ;
它是一個
int
型的數組,長度為2,第一個元素是父view消費的
x
方向的滾動距離;第二個元素是父view消費的
y
方向的滾動距離,如果這兩個值不為0,則子view需要對滾動的量進行一些修正。正因為有了這個參數,使得我們處理滾動事件的時候,思路更加清晰,不會像以前一樣被一堆的滾動參數搞混。
對NestedScroll的介紹暫時到這裡,下一次将講一下
CoordinatorLayout
的使用(其中讓人較難了解的Behavior對象),以及在
SegmentFault Android用戶端
中的實踐。謝謝支援。