当父容器和子控件都支持滚动时的一种协做机制.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);
}
}
}
复制代码