Why
Android事件分發機制一般情況下能滿足大部分情況下View事件處理,其中的關鍵點就是事件攔截和事件上傳,具體的詳情這里不做展開。假設現在有這樣一種場景,ViewGroup有且僅有一個Child View,?
要求是Child消費down事件、ViewGroup消費move事件,這種情況就不太好處理,原因是Child消費down事件后,后續的move、up和cancel的onTouchEvent返回false, 事件都會發送到Child, ViewGroup不會有機會處理??梢钥闯鰀own事件是否消費很關鍵,根據默認的事件分發機制似乎不能完成這里的要求,好在google提供了design(Android L版本提出), design庫其中的一個重要特性就是嵌套滑動機制。?
What
嵌套滑動可以簡單的理解成父(ViewGroup)和子(View或者ViewGroup)共同處理move事件,流程是這樣的:?子View先將滑動事件交給父View處理,?父View根據情況消耗全部或者部分move偏移量,子View再消耗剩余的move偏移量,最后子View再通知父View scroll消耗的情況,父View根據這個需求再進行滑動,整個過程的順序如下:
0,父View先處理scroll
1,子View再處理scroll
2,父View再處理scroll
3,子View再處理scroll
元素
主要接口有2個,分別是 NestedScrollingParent
和 NestedScrollingChild
,父 View 需要實現 NestedScrollingParent接口,而子 View 需要實現 NestedScrollingChild接口。
NestedScrollingChild接口的源碼如下:
public interface NestedScrollingChild {
//設置是否開啟嵌套滑動功能
public void setNestedScrollingEnabled(boolean enabled);
public boolean isNestedScrollingEnabled();
//開始一個新的嵌套滑動,參數指定橫向或者縱向
public boolean startNestedScroll(int axes);
//結束一個已經開始的嵌套
public void stopNestedScroll();
//是否有對應的parent配合處理滑動事件
public boolean hasNestedScrollingParent();
//scroll的預處理過程,對應上圖的0和1
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
//scroll的后處理過程,對應上圖的2和3
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
//fling和scroll和類似
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
}
NestedScrollingParent接口的源碼如下:
public interface NestedScrollingParent {
//是否接受子View的嵌套滑動
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
//成功后的回調
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
//結束后的回調
public void onStopNestedScroll(View target);
//預處理過程,對應上圖的0
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
//后處理過程,對應上圖的2
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
//fling和scroll和類似
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
//橫向還是縱向
public int getNestedScrollAxes();
}
google還提供了2個輔助類來幫助實現整個過程,分別是NestedScrollingChildHelper 和NestedScrollingParentHelper,從名字可以看出分別作用的對象是Child和Parent。
比如Child的用法如下:
// NestedScrollingChild
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
都是直接的調用helper的方法,可以看出google為了減少開發者的學習和開發成本費了很多心思。Parent的helper相比于Child提供的方法就不是那么多了,主要的邏輯主要靠開發者自己實現。
流程
Child | Parent |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
可以看出是一一對應的關系,整個過程是Child主導,Parent配合完成,用文字說明一下整個過程:
1. 準備過程
Child發起一個嵌套滑動處理,調用startNestedScroll.
Parent回調onStartNestedScroll說明收到了來自Child的請求,根據情況決定是否接受Child請求,通過返回值告訴Child結果。
Parent如果接受Child的請求還會回調onNestedScrollAccepted。
2. 處理過程
每次滑動前,Child 先詢問 Parent 是否需要滑動,即 dispatchNestedPreScroll(),這就回調到 Parent 的 onNestedPreScroll(),Parent 可以在這個回調中“劫持”掉 Child 的滑動,也就是先于 Child 滑動。
Child 滑動以后,會調用 onNestedScroll(),回調到 Parent 的 onNestedScroll(),這里就是 Child 滑動后,剩下的給 Parent 處理,也就是 后于 Child 滑動。
3. 結束過程
Child調用stopNestedScroll發起一個結束請求,Parent回調回調onStopNestedScroll處理相關邏輯,比如重置參數等待下次滑動請求
版本兼容
雖然是Api21版本才提出的,google也提供了兼容方案,相關接口再android.support.v4包中