Android NestedScroll 嵌套滑动机制学习笔记

Nested Scroll

当一个可滑动ViewGroup内部包含另一个可滑动View,用户触摸内部可滑动View时,应当首先滑动内部View,内部View滑不动时外部ViewGroup应接着进行滑动

以上描述的是用户预期的体验,但是事实上,这种效果违背了一个事件只有一个View进行消费的约定,因此按照常规的Touch事件分发机制这种效果是很难实现的。所以需要一套全新的滑动机制——Nested Scroll(嵌套滑动)

嵌套滑动是由内部View发起,可由内部View和外部View共同消费一个事件的机制。当检测到一个滑动事件的时候,先由内部View开启嵌套滑动,交由外部View预先进行滑动距离消费,没有消费完的由内部View进行消费,如果内部View还是没有消费完,再交由外部View消费,如此反复,直至消费完为止。消费顺序即parent->child->parent->child->...->child

嵌套滑动是在5.0之后添加到View和ViewGroup中给几乎所有的View增加了支持的一系列代码,如使用频率极高的RecyclerViewNestedScrollView等。而与此同时,Google将其中的主要方法和辅助工具类提炼了出来,以供开发者们在自定义View时使用。

NestedScrollingChild(省略了Fling相关的方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface NestedScrollingChild {
void setNestedScrollingEnabled(boolean enabled);
boolean isNestedScrollingEnabled();
boolean startNestedScroll(@ScrollAxis int axes);
void stopNestedScroll();
boolean hasNestedScrollingParent();
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed,
@Nullable int[] offsetInWindow);
boolean dispatchNestedPreScroll(int dx, int dy,
@Nullable int[] consumed, @Nullable int[] offsetInWindow)
......
}

NestedScrollingParent(省略了Fling相关的方法)

1
2
3
4
5
6
7
8
9
public interface NestedScrollingParent {
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
void onStopNestedScroll(@NonNullView target);
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
......
int getNestedScrollAxes();
}

第二版接口相比于第一版,主要区别是,其中一些方法添加了一个滑动类型参数(触摸、惯性滑动)

NestedScrollingChild2

1
2
3
4
5
6
7
public interface NestedScrollingChild2 extends NestedScrollingChild {
boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type);
void stopNestedScroll(@NestedScrollType int type);
boolean hasNestedScrollingParent(@NestedScrollType int type);
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type);
boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, @NestedScrollType int type);
}

NestedScrollingParent2

1
2
3
4
5
6
7
public interface NestedScrollingParent2  extends NestedScrollingParent {
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type);
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type);
void onStopNestedScroll(@NonNullView target, @NestedScrollType int type);
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type);
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type);
}

第三版接口再一次添加了一个记录已消耗的滑动距离的参数

NestedScrollingChild3

1
2
3
public interface NestedScrollingChild3 extends NestedScrollingChild2 {
void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type, @NonNull int[] consumed);
}

NestedScrollingParent3

1
2
3
public interface NestedScrollingParent3 extends NestedScrollingParent2 {
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type, @NonNull int[] consumed);
}

在自定义View时建议使用第三版的接口和辅助工具类(NestedScrollingParentHelper、NestedScrollingChildHelper)。