五分钟带你看懂 Android NestedScrolling 嵌套滑动机制

Android NestedScrolling嵌套滑动机制
Android在发布5.0之后加入了嵌套滑动机制NestedScrolling,为嵌套滑动提供了更方便的处理方案。在此对嵌套滑动机制进行详细的分析。
嵌套滑动的常见用法比如在滑动列表的时候隐藏相关的TopBar和BottomBar,增加列表的信息展示范围,让用户聚焦于App想展示的内容上等。官方出的Design包里也有很多支持该机制的炫酷控件,比如CoordinatorLayout,AppBarLayout等,在用户体验上有很大的进步。
说道嵌套滑动,离不开以下几个内容:
  • NestedScrollingChild
  • NestedScrollingParent
  • NestedScrollingChildHelper
  • NestedScrollingParentHelper
在具体说明之前,先来看看我们的Sample,这是一个仿携程机票首页的Demo


这里用到了一个实现了NestedScrollingParent的CollaspingLayout作为父View和一个实现了NestedScrollingChild的NestedScrollView作为子View进行嵌套滑动,布局可以简单的描述成:


具体的布局结构大致如下:





从布局可以看到其实在实现了NestedScrollingParent之后就能很方便的完成子View和父View的嵌套滑动,下面就来简单看看上面的四个类是如何使用的,在系统为我们提供的控件中,NestedScrollView是实现了这个机制的控件,以它的实现为例,首先看作为嵌套滑动的子View:



再来看看同样作为嵌套滑动父View的CollaspingLayout的实现:


从上面的实现可以看出,基本上都是通过mParentHelper和mChildHelper来完成滑动的,没接触过这方面的同学看着肯定觉得很难理解,的确有些跳跃性,在说清楚这个问题之前必须先把这几个类之间的交互逻辑理清楚才能不至于不知所云。
先来梳理一下子View和父View的接中都有哪些方法。这种套路一般都是子View发起的然后父View进行回调从而完成配合。
子View 父View
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll
这里的子View指的是实现了NestedScrollingChild的View,例如我们的NestedScrollView,父View指的是实现了NestedScrollingParent的View,比如我们上面写的CollaspingLayout。
首先在子View滑动还未开始之前将调用startNestedScroll,对应NestedScrollView中的ACTION_DOWN:
1
2
3
4
5
6
7
8
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_DOWN: {
......
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);//在接到点击事件之初调用
break;
}
}


那么调用 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)寓意何在?跟进去看到其实是调用mChildHelper.startNestedScroll(axes)的实现:


大家仔细看我在代码里加的注释,需要关心的就是父View在此时需要决定是否跟随子View滑动,看看父View的实现:
1
2
3
4
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}

ViewCompat.SCROLL_AXIS_VERTICAL的值是2(10),所以当nestedScrollAxes 也为2的时候,返回true,回到上面可以看到只要是竖直方向的 滑动,父View就会和子View进行嵌套滑动。而在父View的
onNestedScrollAccepted中,则把滑动的方向给保存下来了。这样父View和子View的第一次合作关系就结束了,再看看接下来是如何配合的。
当子View在滑动的Move事件中,又开始了嵌套滑动:
1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean onTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_MOVE:
final int y = (int) MotionEventCompat.getY(ev, activePointerIndex);
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
deltaY -= mScrollConsumed[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
}

在子View决定滑动的时候,再次在进行自己的滑动前调用dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)


看看父View是怎么处理的,也是实现了这套机制的,看看他是怎么处理的:

1
2
3
4
5
6
7
8
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if (dy > 0 && mHeaderController.canScrollUp()) {
final int delta = moveBy(dy);
consumed[0] = 0;
consumed[1] = delta;
}
}

通过moveby计算父View滑动的距离,并将父ViewY方向消耗的距离记录下来
继续来看子View,在通知了父View并且父View消耗了滑动距离之后,剩下的就是自己进行滑动了


接下来又是父View的回调了,来看看父View的处理:
1
2
3
4
5
6
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed) {
final int myConsumed = moveBy(dyUnconsumed);
final int myUnconsumed = dyUnconsumed - myConsumed;
}

父View在这里将最后子View滑动完后剩余的距离进行收尾处理,自我调整后第二轮的嵌套滑动也结束了。
那么再看看最后一轮滑动:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean onTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
stopNestedScroll();
break;
}

在触控事件的最后一个阶段,也就是ACTION_UP时,调用stopNestedScroll(),这时会通知父View的onStopNestedScroll()来对整个系列的滑动来收尾
1
2
3
4
5
6
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
mNestedScrollingParent = null;
}
}

父类最后在自己的onStopNestedScroll()实现相关的收尾处理,比如重置滑动状态标记,完成动画操作,通知滑动结束等。这样,整个滑动嵌套流程就完成了。
最后来总结一下整个流程,分为三个步骤:
  • 步骤一:子View的ACTION_DOWN调用startNestedScroll—->父View的onStartNestedScroll判断是否要一起滑动,父ViewonNestedScrollAccepted同意协同滑动
  • 步骤二:子View的ACTION_MOVE调用dispatchNestedPreScroll—->父View的onNestedPreScroll在子View滑动之前先进行滑动并消耗需要的距离—->父View完成该次滑动之后返回消耗的距离,子View在剩下的距离中再完成自己需要的滑动
  • 步骤三:子View滑动完成之后调用dispatchNestedScroll—->父View的onNestedScroll处理父View和子View之前滑动剩余的距离
  • 步骤四:子View的ACTION_UP调用stopNestedScroll—->父View的onStopNestedScroll完成滑动收尾工作

这样,子View和父View的一系列嵌套滑动就完成了,可以看出来整个嵌套滑动还是靠子View来推动父View进行滑动的,这也解决了在传统的滑动事件中一旦事件被子View处理了就很难再分享给父View共同处理的问题,这也是嵌套滑动的一个特点。
结语
嵌套滑动作为官方推出的一套更加方便的处理滑动的工具,可以说是很大程度上减少了我们在出来这方面问题上的复杂性,当然,上面提到的仅仅是原理,真正的实现大家可以仔细地去看Design包一些控件的源码来进一步深入了解。

猜你喜欢

转载自blog.csdn.net/small_and_smallworld/article/details/73087653
今日推荐