Android下NestedScrolling机制与CoordinatorLayout之源码分析

1.CoordinatorLayout依赖库

旧版本导入CoordinatorLayout依赖

implementation 'com.android.support:design:28.0.0'

升级Android X后的依赖

implementation 'androidx.coordinatorlayout:coordinatorlayout:1.0.0'

2.NestedScrolling机制介绍

NestedScrolling提供了一套父View和子View滑动交互机制;要完成这样的交互,父View需要实现NestedScrollingParent接口,而子View要实现NestedScrollingChild接口,系统提供了NestedScrollingView控件就实现了这两个接口;

public class NestedScrollView extends FrameLayout implements NestedScrollingParent3,
        NestedScrollingChild3, ScrollingView {}

NestedScrolling(嵌套滑动),就是子View和父View在滑动过程中,互相通信决定某个滑动是子View处理合适,还是父View处理合适;所以Parent和Child之间存在相互调用,NestedScrollView遵循下面的调用关系:

 上图可以这么理解:

  • ACTION_DOWN的时候子View就要调用startNestedScrolling()方法来告诉父View自己要开始滑动了(实质上是寻找能够配合child进行嵌套滑动的parent),parent也会继续向上寻找能够配合自己滑动的parent,可以理解为在做一些准备工作;(确认是否处理这次滑动事件)
  • 父View会收到onStartNestedScroll回调从而决定是不是要配合子view做出响应;如果需要配合此方法返回true;继而onNestedScrollAccepted()回调会被调用;
  • 在滑动事件产生但是子View还没有处理前可以调用;dispatchNestedPreScroll(0,dy,consumed,offsetInWindow)这个方法把事件传给父View,这样父View就能在onNestedPreScroll()方法里面接收到子View的滑动信息,把处理完后的然后做出相应的处理结果通过consumed传递给子View;
  • dispatchNestedPreScroll()之后,child可以进行自己的滚动操作;
  • 如果父View需要在子View滑动后处理相关事件的话可以在子View的事件处理完成之后调用dispatchNestedScroll()然后父View会在onNestedScroll()收到回调;
  • 最后,滑动结束,调用onStopNestedScroll()表示本次处理结束;
  • 但是,如果滑动速度比较大,会触发fling,fling也分为preFling和fling连个阶段,处理过程和scroll基本差不多

NestedScrollingParentNestedScrollingChild都提供了对应非辅助类,NestedScrollingChildHelperNestedScrollingParentHelper辅助类,辅助类会负责处理大部分逻辑,他们之间的调用关系如下:

2.1NestedScrolling机制关键类说明

NestedScrollingChild/2/3:此接口定义NestedScrolling各状态监听回调方法,需要子View去实现NestedScrollingChild接口,然后借助NestedScrollingChildHelper辅助类实现NestedScrolling各状态派发给父视图处理;

NestedScrollingChildHelper:实现了Child和Parent的交互逻辑,将NestedScrolling操作先交给Parent处理;

NestedScrollingParent/2/3:此接口定义接收子View的NestedScrolling各状态监听回调方法,需要父ViewGroup实现NestedScrollingParent接口,然后借助NestedScrollingParentHelper辅助类实现NestedScrolling各状态派处理;

NestedScrollingParentHelper:帮助Parent实现和Child交互的逻辑;滑动动作是Child的主动发起,Parent就受滑动回调并作出响应;

 2.2实现NestedScrollingChild接口

为什么是NestedScrollingChild接口?

NestedScrollingChild定义一套嵌套滑动事件标准方法(从开始startNestedScroll到结束stopNestedScroll),提供给子View实现,子View负责实现NestedScrollingChild的方法,根据事件的动作类型触发嵌套滑动事件标准方法,最终会调用实现了NestedScrollingParent父ViewGroup处理; 简单理解就是我告诉父ViewGroup我要滑动了,你可以先做滑动处理,然后我在处理;

实际上NestedScrollingChildHelper辅助类已经实现好了Child和Parent交互,原来View的处理逻辑滑动事件逻辑上大体不需要改变;需要做的就是,如果准备开始滑动了,需要告诉Parent,Child要准备进入滑动状态了,调用startNestedScroll();Child在滑动之前,先问一个你的Parent是否需要滑动,也就是调用dispatchNestedPreScroll;如果父类消耗了部分滑动事件,Child需要重新计算一下父类消耗后剩余给Child的滑动距离余量;然后,Child自己进行余下的滑动;最后,如果滑动距离还有剩余,Child就再问一下,Parent是否需要在继续滑动你剩下的距离,也就是调用dispatchNestedScroll(), 大概就是这么一回事,当然还会有和Scroll类型的Fling系列方法,但我们这里可以先忽略一下;

NestedScrollViewNestedScrollingChild接口实现都是交给辅助类NestedScrollingChildHelper来处理的,是否需要进行额外的一些操作要根据实际情况来定;

//NestedScrollChild
public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        //...
        mParentHelper = new NestedScrollingParentHelper(this);
        mChildHelper = new NestedScrollingChildHelper(this);

        // ...because why else would you be using this widget?
        setNestedScrollingEnabled(true);
    }

@Override
    public void setNestedScrollingEnabled(boolean enabled) {
        mChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return mChildHelper.isNestedScrollingEnabled();
    }

    //在初始化滚动的时候操作,一般在MotionEvent#ACTION_DOWN的时候调用
    @Override
    public boolean startNestedScroll(int axes) {
        return startNestedScroll(axes, ViewCompat.TYPE_TOUCH);
    }

    @Override
    public void stopNestedScroll() {
        stopNestedScroll(ViewCompat.TYPE_TOUCH);
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
    }
    
    //参数和dispatchNestedPreScroll方法的返回有关联
    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
            int dyUnconsumed, int[] offsetInWindow) {
        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow);
    }
    
    //在消费滚动事件之前调用,提供一个让ViewParent实现联合滚动的机会,因此ViewParent可以消费一部分或者全部的滑动事件,参数consumed会记录ViewParent所消费掉的事件
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

实现NestedScrollingChild接口挺简单的不是吗?但还需要我们决定什么时候进行调用,和调用哪些方法;

startNestedScroll和stopNestedScroll的调用

startNestedScroll配合stopNestedScroll使用,startNestedScroll会再接收到ACTION_DOWN的时候调用,stopNestedScroll会在接收到ACTION_UP|ACTION_CANCEL的时候调用,NestedScrollView中的伪代码是这样

onInterceptTouchEvent | onTouchEvent(MotionEvent ev){
    case MotionEvent.ACTION_DOWN:
        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
    break;
    case MotionEvent.ACTION_UP | ACTION_CANCEL:
        stopNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
    break;
}

NestedScrollingChildHelper处理startNestedScroll方法,可以看出会调用Parent的onStartNestedScrollonStartNestedScrollAccepted方法,只要Parent愿意优先处理这次的滑动事件,在结束的时候Parent还会收到onStopNestedScroll回调;

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }

  public void stopNestedScroll(@NestedScrollType int type) {
        ViewParent parent = getNestedScrollingParentForType(type);
       
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值