標籤:
Google在發布安卓 Lollipop版本之後,為了更好的使用者體驗,Google為Android的滑動機制提供了NestedScrolling特性
NestedScrolling的特性可以體現在哪裡呢?
比如你使用了Toolbar,下面一個ScrollView,向上滾動隱藏Toolbar,向下滾動顯示Toolbar,這裡在邏輯上就是一個NestedScrolling —— 因為你在滾動整個Toolbar在內的View的過程中,又嵌套滾動了裡面的ScrollView。
在這之前,我們知道Android對Touch事件的分發是有自己一套機制的。主要是有是三個函數:
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
這種分發機制讓行動裝置 App安全檢測平台-愛內測(ineice.com)發現有一個漏洞,據愛內測的CTO介紹:
如果子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
publicvoid setNestedScrollingEnabled(boolean enabled) {
super.setNestedScrollingEnabled(enabled);
mChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
publicboolean isNestedScrollingEnabled() {
returnmChildHelper.isNestedScrollingEnabled();
}
@Override
publicboolean startNestedScroll(int axes) {
returnmChildHelper.startNestedScroll(axes);
}
@Override
publicvoid stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
@Override
publicboolean hasNestedScrollingParent() {
returnmChildHelper.hasNestedScrollingParent();
}
@Override
publicboolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,int dyUnconsumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
publicboolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[]offsetInWindow) {
returnmChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
publicboolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed){
returnmChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
publicboolean dispatchNestedPreFling(float velocityX, float velocityY) {
returnmChildHelper.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.ViewView} subclass
*method/{@link NestedScrollingChild} interface method with the same signature toimplement
*the standard policy.</p>
*
*@param axes Supported nested scroll axes.
* See {@linkNestedScrollingChild#startNestedScroll(int)}.
*@return true if a cooperating parent view was found and nested scrollingstarted successfully
*/
publicboolean 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參數。
publicboolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[]offsetInWindow) ;
它是一個int型的數組,長度為2,第一個元素是父view消費的x方向的滾動距離;第二個元素是父view消費的y方向的滾動距離,如果這兩個值不為0,則子view需要對滾動的量進行一些修正。正因為有了這個參數,使得我們處理滾動事件的時候,思路更加清晰,不會像以前一樣被一堆的滾動參數搞混。
淺析:Android 嵌套滑動機制(NestedScrolling)