Android 嵌套滑動機制(NestedScrolling)

谷歌在發(fā)布安卓 Lollipop版本之后,為了更好的用戶體驗,Google為Android的滑動機制提供了NestedScrolling特性

NestedScrolling的特性可以體現(xiàn)在哪里呢?

比如你使用了Toolbar,下面一個ScrollView,向上滾動隱藏Toolbar,向下滾動顯示Toolbar,這里在邏輯上就是一個NestedScrolling?——?因為你在滾動整個Toolbar在內(nèi)的View的過程中,又嵌套滾動了里面的ScrollView。

在這之前,我們知道Android對Touch事件的分發(fā)是有自己一套機制的。主要是有是三個函數(shù):

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

這種分發(fā)機制讓移動應(yīng)用安全檢測平臺-愛內(nèi)測(ineice.com)發(fā)現(xiàn)有一個漏洞,據(jù)愛內(nèi)測的CTO介紹:

如果子view獲得處理touch事件機會的時候,父view就再也沒有機會去處理這個touch事件了,直到下一次手指再按下。

也就是說,我們在滑動子View的時候,如果子View對這個滑動事件不想要處理的時候,只能拋棄這個touch事件,而不會把這些傳給父view去處理。

但是Google新的NestedScrolling機制就很好的解決了這個問題。

我們看看如何實現(xiàn)這個NestedScrolling,首先有幾個類(接口)我們需要關(guān)注一下

NestedScrollingChild

NestedScrollingParent

NestedScrollingChildHelper

NestedScrollingParentHelper

以上四個類都在support-v4包中提供,Lollipop的View默認實現(xiàn)了幾種方法。

實現(xiàn)接口很簡單,這邊我暫時用到了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);

}

對,簡單的話你就這么實現(xiàn)就好了。

這些接口都是我們在需要的時候自己調(diào)用的。childHelper干了些什么事呢?,看一下startNestedScroll方法

/**

* Start a new nested scroll for this view.

*

*

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.

*

* @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;

}

可以看到這里是幫你實現(xiàn)一些跟NestedScrollingParent交互的一些方法。

ViewParentCompat是一個和父view交互的兼容類,它會判斷api version,如果在Lollipop以上,就是用view自帶的方法,否則判斷是否實現(xiàn)了NestedScrollingParent接口,去調(diào)用接口的方法。

那么具體我們怎么使用這一套機制呢?比如子View這時候我需要通知父view告訴它我有一個嵌套的touch事件需要我們共同處理。那么針對一個只包含scroll交互,它整個工作流是這樣的:

一、startNestedScroll

首先子view需要開啟整個流程(內(nèi)部主要是找到合適的能接受nestedScroll的parent),通知父View,我要和你配合處理TouchEvent

二、dispatchNestedPreScroll

在子View的onInterceptTouchEvent或者onTouch中(一般在MontionEvent.ACTION_MOVE事件里),調(diào)用該方法通知父View滑動的距離。該方法的第三第四個參數(shù)返回父view消費掉的scroll長度和子View的窗體偏移量。如果這個scroll沒有被消費完,則子view進行處理剩下的一些距離,由于窗體進行了移動,如果你記錄了手指最后的位置,需要根據(jù)第四個參數(shù)offsetInWindow計算偏移量,才能保證下一次的touch事件的計算是正確的。

如果父view接受了它的滾動參數(shù),進行了部分消費,則這個函數(shù)返回true,否則為false。

這個函數(shù)一般在子view處理scroll前調(diào)用。

三、dispatchNestedScroll

向父view匯報滾動情況,包括子view消費的部分和子view沒有消費的部分。

如果父view接受了它的滾動參數(shù),進行了部分消費,則這個函數(shù)返回true,否則為false。

這個函數(shù)一般在子view處理scroll后調(diào)用。

四、stopNestedScroll

結(jié)束整個流程。

整個對應(yīng)流程是這樣

子view???父view

startNestedScroll?onStartNestedScroll、onNestedScrollAccepted

dispatchNestedPreScroll????onNestedPreScroll

dispatchNestedScroll??onNestedScroll

stopNestedScroll?onStopNestedScroll

一般是子view發(fā)起調(diào)用,父view接受回調(diào)。

我們最需要關(guān)注的是dispatchNestedPreScroll中的consumed參數(shù)。

public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) ;

它是一個int型的數(shù)組,長度為2,第一個元素是父view消費的x方向的滾動距離;第二個元素是父view消費的y方向的滾動距離,如果這兩個值不為0,則子view需要對滾動的量進行一些修正。正因為有了這個參數(shù),使得我們處理滾動事件的時候,思路更加清晰,不會像以前一樣被一堆的滾動參數(shù)搞混。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容