NestedScrollView
是 Android 中的一个特殊滚动视图,它继承自 ScrollView
,但添加了对嵌套滚动(nested scrolling)的支持。这使得 NestedScrollView
可以与其它支持嵌套滚动的视图(如 RecyclerView
)协同工作,从而实现更复杂的滚动效果。
下面我们将结合源码来分析 NestedScrollView
的实现原理。
1. NestedScrollView 类定义
NestedScrollView
继承自 ScrollView
,因此它也只接受一个直接子视图。与 ScrollView
不同的是,它支持嵌套滚动,这意味着它可以在内部滚动时允许其子视图也进行滚动。
1public class NestedScrollView extends ScrollView implements NestedScrollingParent, NestedScrollingChild {
2
3 // ...
4
5 // 构造函数
6 public NestedScrollView(Context context) {
7 super(context);
8 initScrollView();
9 }
10
11 public NestedScrollView(Context context, AttributeSet attrs) {
12 super(context, attrs);
13 initScrollView();
14 }
15
16 public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
17 super(context, attrs, defStyleAttr);
18 initScrollView();
19 }
20
21 private void initScrollView() {
22 // 初始化操作
23 setFillViewport(true); // 填充整个视口
24 setFocusable(true); // 可聚焦
25 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); // 子视图优先获得焦点
26 }
27
28 // 其他方法...
29}
2. 测量过程(onMeasure)
NestedScrollView
的测量过程与 ScrollView
类似,但是它还需要考虑嵌套滚动的情况。
1@Override
2protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
4
5 // 如果有嵌套滚动的子视图,则需要考虑子视图的滚动情况
6 // 如果子视图是嵌套滚动的,那么需要在测量时考虑到子视图的滚动距离
7 if (hasNestedScrollingChild()) {
8 final View child = getChildAt(0);
9 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
10 child.getMeasuredHeight() + getScrollY(), MeasureSpec.EXACTLY);
11 child.measure(getChildMeasureSpec(widthMeasureSpec,
12 getPaddingLeft() + getPaddingRight(), child.getLayoutParams().width),
13 childHeightMeasureSpec);
14 }
15}
3. 布局过程(onLayout)
布局过程与 ScrollView
相似,但需要考虑嵌套滚动的情况。
1@Override
2protected void onLayout(boolean changed, int l, int t, int r, int b) {
3 final int width = r - l;
4 final int height = b - t;
5 final int paddingLeft = getPaddingLeft();
6 final int paddingTop = getPaddingTop();
7 final int paddingRight = getPaddingRight();
8 final int paddingBottom = getPaddingBottom();
9
10 final int childWidth = width - paddingLeft - paddingRight;
11 final int childHeight = height - paddingTop - paddingBottom;
12
13 final View child = getChildAt(0);
14 if (child != null) {
15 final int childLeft = paddingLeft;
16 final int childTop = paddingTop;
17 child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
18 }
19}
4. 滚动处理
NestedScrollView
重写了 onTouchEvent
方法来处理触摸事件,并实现滚动功能。它还实现了 NestedScrollingParent
和 NestedScrollingChild
接口中的方法。
1@Override
2public boolean onTouchEvent(MotionEvent ev) {
3 // ...
4 switch (ev.getActionMasked()) {
5 case MotionEvent.ACTION_DOWN:
6 // ...
7 break;
8 case MotionEvent.ACTION_MOVE:
9 // ...
10 break;
11 case MotionEvent.ACTION_UP:
12 // ...
13 break;
14 case MotionEvent.ACTION_CANCEL:
15 // ...
16 break;
17 }
18 return true;
19}
5. 嵌套滚动接口实现
NestedScrollView
实现了 NestedScrollingParent
和 NestedScrollingChild
接口,以便支持嵌套滚动。
1@Override
2public boolean startNestedScroll(int axes) {
3 // ...
4 return true;
5}
6
7@Override
8public void stopNestedScroll() {
9 // ...
10}
11
12@Override
13public boolean hasNestedScrollingParent() {
14 // ...
15 return false;
16}
17
18@Override
19public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
20 // ...
21}
22
23@Override
24public void dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
25 // ...
26}
27
28@Override
29public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
30 // ...
31 return false;
32}
33
34@Override
35public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
36 // ...
37 return false;
38}
6. 回调方法
NestedScrollView
提供了 OnScrollChangeListener
接口,允许开发者监听滚动事件。
1public interface OnScrollChangeListener {
2 void onScrollChange(NestedScrollView who, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
3}
7. 平滑滚动
NestedScrollView
使用 AutoScrollHelper
类来实现平滑滚动动画。
1public void smoothScrollTo(int destX, int destY) {
2 AutoScrollHelper.getInstance(this).ensureTarget(this);
3 AutoScrollHelper.getInstance(this).startAnimation(destX, destY);
4}
总结
NestedScrollView
的实现原理与 ScrollView
类似,但是它增加了对嵌套滚动的支持。这使得它可以更好地与其他支持嵌套滚动的视图协同工作。通过重写 onMeasure
和 onLayout
方法,NestedScrollView
确保子视图正确地显示在屏幕上,并且可以通过处理触摸事件来响应用户的滚动操作。此外,它还实现了 NestedScrollingParent
和 NestedScrollingChild
接口,以支持嵌套滚动场景。