當父容器和子控件都支持滾動時的一種協做機制.android
1.應對事件分發機制的侷限性:
父容器一旦攔截事件本身處理,後續事件不能再傳遞給子控件消費.markdown
2.事件衝突的一種解決方案oop
先滾動父容器再滾動子控件:post
處理手勢衝突:this
處理手勢衝突:spa
嵌套滾動機制從android5.0中加入,5.0之前的兼容方案由四個類支持:code
接口類:
NestedScrollingChild
NestedScrollingParent
實現類:
NestedScrollingChildHelper
NestedScrollingParentHelper
orm
sequenceDiagram NestedScrollingChild->>NestedScrollingParent: 子控件觸發嵌套滾動:startNestedScroll() NestedScrollingParent->>NestedScrollingChild: onStartNestedScroll() loop 1-n個滾動事件 Note over NestedScrollingParent,NestedScrollingChild: 1.父容器優先消費 NestedScrollingChild->>NestedScrollingParent: dispatchNestedPreScroll() NestedScrollingParent->>NestedScrollingChild: onNestedPreScroll() Note over NestedScrollingParent,NestedScrollingChild: 2.子控件消費滾動 NestedScrollingChild->>NestedScrollingParent: dispatchNestedScroll() NestedScrollingParent->>NestedScrollingChild: onNestedScroll() Note over NestedScrollingParent,NestedScrollingChild: 3.父容器處理未被消費的滾動距離 end rect rgb(0, 255, 255) opt 0-1個慣性滾動 NestedScrollingChild-->>NestedScrollingParent: dispatchNestedPreFling() NestedScrollingParent-->>NestedScrollingChild: onNestedPreFling() NestedScrollingChild-->>NestedScrollingParent: dispatchNestedFling() NestedScrollingParent-->>NestedScrollingChild: onNestedFling() end end NestedScrollingChild-->>NestedScrollingParent: 子控件結束嵌套滾動:stopNestedScroll() NestedScrollingParent->>NestedScrollingChild: onStopNestedScroll()
NestedScrollingChild
類圖接口
classDiagram NestedScrollingChild <|-- NestedScrollingChild2 NestedScrollingChild2 <|-- NestedScrollingChild3 NestedScrollingChild2 <|.. RecyclerView NestedScrollingChild3 <|.. RecyclerView NestedScrollingChild3 <|.. NestedScrollView <<interface>> NestedScrollingChild class NestedScrollingChild{ +setNestedScrollingEnabled(boolean) +isNestedScrollingEnabled() +startNestedScroll(int) +stopNestedScroll() +hasNestedScrollingParent() +dispatchNestedScroll(int, int,int, int, int[]) +dispatchNestedPreScroll(int, int, int[], int[]) +dispatchNestedFling(float, float, boolean) +dispatchNestedPreFling(float, float) } <<interface>> NestedScrollingChild2 class NestedScrollingChild2{ +startNestedScroll(int,int) +stopNestedScroll(int) +hasNestedScrollingParent(int) +dispatchNestedScroll(int, int,int, int, int[],int) +dispatchNestedPreScroll(int, int, int[], int[],int) } <<interface>> NestedScrollingChild3 class NestedScrollingChild3{ +dispatchNestedScroll(int, int,int, int, int[],int,int[]) } class RecyclerView class NestedScrollView
NestedScrollingParent
類圖事件
classDiagram NestedScrollingParent <|-- NestedScrollingParent2 NestedScrollingParent2 <|-- NestedScrollingParent3 NestedScrollingParent2 <|.. CoordinatorLayout NestedScrollingParent3 <|.. CoordinatorLayout NestedScrollingParent3 <|.. NestedScrollView NestedScrollingParent3 <|.. MotionLayout <<interface>> NestedScrollingParent class NestedScrollingParent{ +onStartNestedScroll(View,View, @ScrollAxis int) +onNestedScrollAccepted(View,View, @ScrollAxis int) +onStopNestedScroll(View) +onNestedScroll(View, int, int, int, int) +onNestedPreScroll(View, int, int,int[]) +onNestedFling(View, float, float, boolean) +onNestedPreFling(View, float, float) +getNestedScrollAxes() } <<interface>> NestedScrollingParent2 class NestedScrollingParent2{ +onStartNestedScroll(View,View, @ScrollAxis int, @NestedScrollType int) +onNestedScrollAccepted(View,View, @ScrollAxis int, @NestedScrollType int) +onStopNestedScroll(View, @NestedScrollType int) +onNestedScroll(View, int, int, int, int, @NestedScrollType int) +onNestedPreScroll(View, int, int,int[], @NestedScrollType int) } <<interface>> NestedScrollingParent3 class NestedScrollingParent3{ +onNestedScroll(View, int, int, int, int, @NestedScrollType int,int[]) } class CoordinatorLayout class NestedScrollView
NestedScrollingChildHelper
類圖
classDiagram class NestedScrollingChildHelper{ +NestedScrollingChildHelper(View) +setNestedScrollingEnabled(boolean) +isNestedScrollingEnabled() +startNestedScroll(int) +stopNestedScroll() +hasNestedScrollingParent() +dispatchNestedScroll(int, int,int, int, int[]) +dispatchNestedPreScroll(int, int, int[], int[]) +dispatchNestedFling(float, float, boolean) +dispatchNestedPreFling(float, float) +onDetachedFromWindow() +onStopNestedScroll(View) }
NestedScrollingParentHelper
類圖
classDiagram class NestedScrollingParentHelper{ +NestedScrollingParentHelper(ViewGroup) +onNestedScrollAccepted(View,View, @ScrollAxis int) +onStopNestedScroll(View) +getNestedScrollAxes() }
5.0之後嵌套滾動的實現被寫進了View
和ViewGroup
裏:
sequenceDiagram View->>ViewGroup: 子控件觸發嵌套滾動:startNestedScroll() ViewGroup->>View: onStartNestedScroll() loop 1-n個滾動事件 Note over ViewGroup,View: 1.父容器優先消費 View->>ViewGroup: dispatchNestedPreScroll() ViewGroup->>View: onNestedPreScroll() Note over ViewGroup,View: 2.子控件消費滾動 View->>ViewGroup: dispatchNestedScroll() ViewGroup->>View: onNestedScroll() Note over ViewGroup,View: 3.父容器處理未被消費的滾動距離 end rect rgb(0, 255, 255) opt 0-1個慣性滾動 View-->>ViewGroup: dispatchNestedPreFling() ViewGroup-->>View: onNestedPreFling() View-->>ViewGroup: dispatchNestedFling() ViewGroup-->>View: onNestedFling() end end View-->>ViewGroup: 子控件結束嵌套滾動:stopNestedScroll() ViewGroup->>View: onStopNestedScroll()
View
和ViewGroup
中的嵌套滾動相關方法:
classDiagram View <|-- ViewGroup class View{ +setNestedScrollingEnabled(boolean) +isNestedScrollingEnabled() +startNestedScroll(int) +stopNestedScroll() +hasNestedScrollingParent() +dispatchNestedScroll(int, int,int, int, int[]) +dispatchNestedPreScroll(int, int, int[], int[]) +dispatchNestedFling(float, float, boolean) +dispatchNestedPreFling(float, float) } class ViewGroup{ +onStartNestedScroll(View,View, @ScrollAxis int) +onNestedScrollAccepted(View,View, @ScrollAxis int) +onStopNestedScroll(View) +onNestedScroll(View, int, int, int, int) +onNestedPreScroll(View, int, int,int[]) +onNestedFling(View, float, float, boolean) +onNestedPreFling(View, float, float) +getNestedScrollAxes() }
嵌套滾動的觸發和中止
開始嵌套滾動:
NestedScrollingChild.startNestedScroll(int axes)
NestedScrollingChild.stopNestedScroll()
複製代碼
中止嵌套滾動:
NestedScrollingChild.setNestedScrollingEnabled(false)
NestedScrollingChild.stopNestedScroll()
複製代碼
嵌套滾動和事件分發機制的關係(以RecyclerView爲例)
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
case MotionEvent.ACTION_DOWN: {
...
//1.按下時開始嵌套滾動
startNestedScroll(nestedScrollAxis, TYPE_TOUCH); }
break;
case MotionEvent.ACTION_MOVE: {
...
//2.移動時給父容器優先消費
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
//子控件能夠消費的距離=移動的總距離-父控件已經消費的距離
dx -= mScrollConsumed[0]; dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); // Updated the nested
offsets mNestedOffsets[0] += mScrollOffset[0];
...
//3.內部通過scrollByInternal()實現滾動,而後將已消費的距離和未消費的距離傳遞給父控件處理
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
...
}
}break;
case MotionEvent.ACTION_UP: {
...
//4.內部執行fling()方法優先給父容器執行慣性滑動的機會
if (!dispatchNestedPreFling(velocityX, velocityY)) {
...
//5.父控件不消費慣性滾動,子控件自行處理.給父容器觀察子控件fling事件的機會
dispatchNestedFling(velocityX, velocityY, canScroll);
...
mViewFlinger.fling(velocityX, velocityY);
//內部經過resetTouch()方法結束嵌套滾動
stopNestedScroll(TYPE_TOUCH);
}
}break;
}
複製代碼
嵌套fling的版本差別性
support庫v25之前:fling事件不支持部分消費.父容器要麼攔截整個fling事件,要麼交給子控件處理.
support庫v26之後:新增了父容器部分消費fling事件的支持
核心實現是NestedScrollingChild2
和NestedScrollingParent2
這兩個類,重載了嵌套滾動相關方法,增長了type參數.以前的nestedscroll只有手動觸發一種,如今有兩種嵌套滾動:
手動觸發-TYPE_TOUCH
=0
代碼觸發-TYPE_NON_TOUCH
=1
nestedfling就被轉換成了TYPE_NON_TOUCH
類型的nestedscroll,間接的支持了嵌套fling的部分消費.
以NestedScrollView
爲例:
fling方法開始代碼觸發的嵌套滾動:
public void fling(int velocityY) {
if (this.getChildCount() > 0) {
this.mScroller.fling(this.getScrollX(), this.getScrollY(), 0, velocityY, 0, 0, -2147483648, 2147483647, 0, 0);
this.runAnimatedScroll(true);
}
}
private void runAnimatedScroll(boolean participateInNestedScrolling) {
if (participateInNestedScrolling) {
//第二個參數是type,1對應TYPE_NON_TOUCH
this.startNestedScroll(2, 1);
} else {
this.stopNestedScroll(1);
}
this.mLastScrollerY = this.getScrollY();
ViewCompat.postInvalidateOnAnimation(this);
}
複製代碼
在computeScroll方法中調用dispatchNestedPreScroll()和dispatchNestedScroll()方法:
public void computeScroll() {
if (!this.mScroller.isFinished()) {
...
//1.給父容器優先消費
this.dispatchNestedPreScroll(0, unconsumed, this.mScrollConsumed, (int[])null, 1);
//子控件可滾動的距離=總距離-父容器消費的部分
unconsumed -= this.mScrollConsumed[1];
...
if (unconsumed != 0) {
mode = this.getScrollY();
//2.執行子控件自身的滾動
this.overScrollByCompat(0, unconsumed, this.getScrollX(), mode, 0, range, 0, 0, false);
...
//3.將已消費的距離和未消費的距離傳遞給父容器處理
this.dispatchNestedScroll(0, scrolledByMe, 0, unconsumed, this.mScrollOffset, 1, this.mScrollConsumed);
...
}
...
if (!this.mScroller.isFinished()) {
ViewCompat.postInvalidateOnAnimation(this);
} else {
//4.滾性滾動結束時中止代碼觸發的嵌套滾動
this.stopNestedScroll(1);
}
}
}
複製代碼