十分钟Android中的嵌套滚动机制

本文是对嵌套滚动机制的“启蒙教育”,十分钟快速理解嵌套滚动是什么及其基本实现原理,为进一步的深入学习做好铺垫。

从是什么开始

我们先来看一个动图,直观的感受下什么是嵌套滚动(nested scrolling):

十分钟Android中的嵌套滚动机制_第1张图片

既然是嵌套,就说明是一层套着一层,存在两个滚动行为。在上图中,当我们滚动下面的UI控件时,先滚动的却是外头的父容器,当父容器滚动到一定程度后,下面的UI控件才开始滚动。这样看来,确实存在着两个滚动行为。

那么嵌套关系体现在哪呢?我们先来看下实现上图效果的布局文件结构:



    

    
        
    


可以看到,ImageView和NestedScrollView都是ParentView的子View,ParentView使我们自定义的一个继承自LinearLayout的布局管理器。实际上,ParentView类实现了NestedScrollingParent接口,NestedScrollView实现了NestedScrollingChild接口。从名字上我们可以做出这样的猜测:嵌套滚动中需要一个子View和一个作为容器的父View,子View需要实现NestedScrollingChild接口,父View需要实现NestedScrollingParent接口。分别实现了上述两个接口的父View把子View套起来,就可以实现所谓的嵌套滚动。

现在,我们知道了嵌套滚动中的两个主角:

  • 一个实现了NestedScrollingParent的父容器,在本文的其余部分,我们简称为nestedParent;
  • 一个实现了NestedScrollingChild的子View,下文简称为nestedChild。

本文接下来的部分会以上图效果为例,讲解嵌套滚动究竟是如何实现的。

NestedScrollingParent接口

我们来看下本文例子中会涉及到的NestedScrollingParent接口中的方法:

  • onStartNestedScroll(View child, View target, int nestedScrollAxes):当nestedChild想要进行嵌套滚动时,会调用nestedParent的这个方法。这个芳法用于指示是否支持嵌套滚动,比如我们只想支持垂直方向上的嵌套滚动,可以在nestedParent中这样实现这个方法:
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {    
    if (nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL) {        
      return true;    
    }    
    return false;
}
  • onNestedPreScroll(View target, int dx, int dy, int[] consumed):当我们滚动nestedChild时,nestedChild进行实际的滚动前,会先调用nestParent的这个方法。nestedParent在这个方法中可以把子View想要滚动的距离消耗掉一部分或是全部消耗,比如我们的例子中,当我们向上滚动nestedChild时,nestParent会抢在它前头先滚动,直到ImageView完全隐藏,才让nestedChild开始滚动。

在这个例子中,我们自定义的ParentView继承了LinearLayout类,实现了NestedScrollingParent接口,并重写了上面提到的两个方法。相关代码如下:

public class ParentView extends LinearLayout implements NestedScrollingParent{
    int ivHeight = 300;
    . . . 
    @Override
    public boolean onStartNestedScroll(...) {
        . . .
    }

    @Override
    public void onNestedPreScroll(...,int dy,int[] consumed) {
        if ((dy > 0 && getScrollY() < ivHeight) ||
                (dy < 0 && getScrollY() > 0)) {
            consumed[1] = dy;
            scrollBy(dx, dy);
        }
    }

}

onStartNestedScroll()方法的实现上面我们已经介绍过,这里不再赘述。简单说下onNestedPreScroll()方法的实现。不过在这之前我们补下滚动相关的知识。

滚动相关知识补充

先来看一张图:


十分钟Android中的嵌套滚动机制_第2张图片

在上图中,黑色边框代表了View的边框,蓝色边框代表了View的内容的边框。其实我们平时对ListView等控件进行滚动时,实际滚动的是View的内容。比如在上图中,我们向右滚动一个控件,可以看到,实际上是它的内容向右进行滚动了,View的边界线的位置始终是固定的。上图中蓝色右边框和黑色右边框间的距离就是View滚动的距离。
每个View都有名为mScrollX和mScrollY的两个成员变量,前者记录了View在水平方向上滚动的距离,后者记录了View在竖直方向上滚动的距离。mScrollX的绝对值为View的左边框与View的内容的左边框的距离,当View向左滚动时,mScrollX是正的;当View向右滚动时,mScrollX是负的。mScrollY的绝对值为View的上边框与View的内容的上边框的距离,当View向上滚动时,mScrollY是正的;当View向下滚动时,mScrollY是负的。
理解了上面的内容后,让我们看看onNestedPreScroll()为什么要像上面那样实现。

在onNestedPreScroll()方法中,参数dy代表了本次NestedScrollView想要滑动的距离。若我们向上滑动NestedScrollView,dy就是正的,向下就是负的。getScorllY()会返回ParentView的mScrollY参数,为正则表示当前ParentView的内容已经向上滚动了一段距离,否则表示向下滚动过一段距离。ivHeight表示ImageView的高度。理解了上面这些,这个方法的逻辑就很好理解了,这里不再赘述。

NestedScrollingChild接口

我们在布局文件中使用的NestedScrollView就实现了NestedScrollingChild接口。当我们滚动nestedChild时,这个接口的方法会先于nestedParent中的方法被调用。这里我们介绍下本文例子中涉及到的方法:

  • startNestedScroll(int axes):开始沿着参数中指定的方向(水平 or 垂直)进行嵌套滚动
  • dispatchNestedPreScroll(...):这个方法会调用nestedParent的onNestedPreScroll()方法。这样就使得nestedParent有机会抢在NestedScroll之前消耗滚动事件。

嵌套滚动工作原理探索

现在相信各位同学都了解了如何实现基本的嵌套滚动,那么大家是否能够猜到它的实现原理呢?实际上,是nestedChild的onTouchEvent()方法中会对发生的Touch事件进行判断,若为DOWN事件则会调用startNestedScroll()方法;若为MOVE事件则会调用dispatchNestedPreScroll()方法。我们来看下NestedScrollView的onTouchEvent()方法:

public boolean onTouchEvent(MotionEvent ev) {
  . . . 
  final int actionMasked = . . .;
  . . . 
  switch (actionMasked) {
    case MotionEvent.ACTION_DOWN: {
      . . .
      startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
      break;
    }
    case MotionEvent.ACTION_MOVE:
      . . .
      if (dispatchNestedPreScroll(...) {
        deltaY -= mScrollConsumed[1];
        . . .
      }
  . . .
}

我们可以看到,对于DOWN事件,确实会调用startNestedScroll()方法;在MOVE事件时,调用了dispatchNestedPreScroll()方法,deltaY表示nestedChild实际应该滚动的距离,我们可以看到它的值是本该滚动的距离减去nestedParent已经消耗掉的距离。

到这里,对嵌套滚动的启蒙介绍就完毕了:)想要深入了解嵌套滚动机制的同学可以参考官方文档中对NestedScrollingParent等接口的介绍或是这篇文章


长按或扫描二维码关注我们,让您利用每天等地铁的时间就能学会怎样写出优质app。

十分钟Android中的嵌套滚动机制_第3张图片

你可能感兴趣的:(十分钟Android中的嵌套滚动机制)