首页 > 学院 > 开发设计 > 正文

从源码角度分析NestedScrolling

2019-11-09 16:39:30
字体:
来源:转载
供稿:网友

通过CoordinatorLayout可以实现许多炫酷的效果,大家可以参考我之前一篇博客:

一起玩转CoordinatorLayout

其实CoordinatorLayout就是利用NestedScrolling(嵌套滑动机制)来完成复杂的滑动交互。NestedScrolling是Android 5.0之后为我们提供的新特性,降低了使用传统事件分发机制处理嵌套滑动的难度,用于给子view与父view提供更好的交互。

今天就从源码的角度一起分析NestedScrolling,关于NestedScrolling的实现,有以下几个主要类需要关注:

NestedScrollingParent 嵌套滑动父view接口 NestedScrollingChild 嵌套滑动子view接口 NestedScrollingParentHelper 嵌套滑动父view接口的代理实现 NestedScrollingChildHelper 嵌套滑动子view接口的代理实现

我们先来看看NestedScrollingParent中的几个实现方法:

/** * 父View是否允许嵌套滑动 * * @param child 包含嵌套滑动父类的子View * @param target 实现嵌套滑动的子View * @param nestedScrollAxes 嵌套滑动方向,水平竖直或都支持 */ @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return super.onStartNestedScroll(child, target, nestedScrollAxes); } /** * onStartNestedScroll()方法返回true会调用该函数 * 参数与onStartNestedScroll一致 */ @Override public void onNestedScrollAccepted(View child, View target, int axes) { super.onNestedScrollAccepted(child, target, axes); } /** * 嵌套滑动结束时调用 * * @param target 实现嵌套滑动的子View */ @Override public void onStopNestedScroll(View target) { super.onStopNestedScroll(target); } /** * 嵌套滑动子View的滑动情况(进度) * * @param target 实现嵌套滑动的子View * @param dxConsumed 水平方向上嵌套滑动的子View消耗(滑动)的距离 * @param dyConsumed 竖直方向上嵌套滑动的子View消耗(滑动)的距离 * @param dxUnconsumed 水平方向上嵌套滑动的子View未消耗(未滑动)的距离 * @param dyUnconsumed 竖直方向上嵌套滑动的子View未消耗(未滑动)的距离 */ @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); } /** * 嵌套滑动子View滑动之前的准备工作 * * @param target 实现嵌套滑动的子View * @param dx 水平方向上嵌套滑动的子View滑动的总距离 * @param dy 竖直方向上嵌套滑动的子View滑动的总距离 * @param consumed consumed[0]水平方向与consumed[1]竖直方向上父View消耗(滑动)的距离 */ @Override public void onNestedPReScroll(View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(target, dx, dy, consumed); } /** * 嵌套滑动子View的fling(滑行)情况 * * @param target 实现嵌套滑动的子View * @param velocityX 水平方向上的速度 * @param velocityY 竖直方向上的速度 * @param consumed 子View是否消耗fling * @return true 父View是否消耗了fling */ @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return super.onNestedFling(target, velocityX, velocityY, consumed); } /** * 嵌套滑动子View fling(滑行)前的准备工作 * * @param target 实现嵌套滑动的子View * @param velocityX 水平方向上的速度 * @param velocityY 竖直方向上的速度 * @return true 父View是否消耗了fling */ @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return super.onNestedPreFling(target, velocityX, velocityY); } /** * 嵌套滑动方向 * * @return 水平竖直或都支持 */ @Override public int getNestedScrollAxes() { return super.getNestedScrollAxes(); }

接下来看看NestedScrollingChild中的实现方法:

/** * 设置是否支持嵌套滑动 * * @param enabled true与false表示支持与不支持 */ @Override public void setNestedScrollingEnabled(boolean enabled) { super.setNestedScrollingEnabled(enabled); } /** * 判断嵌套滑动是否可用 * * @return true表示支持嵌套滑动 */ @Override public boolean isNestedScrollingEnabled() { return super.isNestedScrollingEnabled(); } /** * 开始嵌套滑动 * * @param axes 方向轴,水平方向与竖直方向 * @return */ @Override public boolean startNestedScroll(int axes) { return super.startNestedScroll(axes); } /** * 停止嵌套滑动 */ @Override public void stopNestedScroll() { super.stopNestedScroll(); } /** * 判断父View是否支持嵌套滑动 * * @return true与false表示支持与不支持 */ @Override public boolean hasNestedScrollingParent() { return super.hasNestedScrollingParent(); } /** * 处理滑动事件 * * @param dxConsumed 水平方向上消耗(滑动)的距离 * @param dyConsumed 竖直方向上消耗(滑动)的距离 * @param dxUnconsumed 水平方向上未消耗(未滑动)的距离 * @param dyUnconsumed 竖直方向上未消耗(未滑动)的距离 * @param offsetInWindow 窗体偏移量 * @return true表示事件已经分发,false表示没有分发 */ @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } /** * 处理滑动事件前的准备工作 * * @param dx 水平方向上滑动的距离 * @param dy 竖直方向上滑动的距离 * @param consumed 父view消耗的距离 * @param offsetInWindow 窗体偏移量 * @return 父View是否处理了嵌套滑动 */ @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } /** * fling(滑行)前的准备工作 * * @param velocityX 水平方向上的速度 * @param velocityY 竖直方向上的速度 * @param consumed 是否被消耗 * @return true表示被消耗,false反之 */ @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return super.dispatchNestedFling(velocityX, velocityY, consumed); } /** * fling(滑行)时调用 * * @param velocityX 水平方向上的速度 * @param velocityY 竖直方向上的速度 * @return true表示被消耗,false反之 */ @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return super.dispatchNestedPreFling(velocityX, velocityY); }

实际应用中,嵌套滑动中的父view实现NestedScrollingParent接口,嵌套滑动中的子view实现NestedScrollingChild接口。NestedScrollingParentHelper和NestedScrollingChildHelper是两个辅助类,我们只需要在对应的接口方法中调用这些辅助类的实现即可。

OK,准备工作到此结束。参考网上资料写了一个简单的例子,先看最终的效果图:

这里写图片描述

最终实现的效果如上所示,通过这个实例来分析完整的嵌套滑动流程以及它们之间的分工合作。

1.子view是嵌套滑动的发起者,父view是嵌套滑动的处理者。首先在子view中允许设置嵌套滑动:

private void init() { nestedScrollingChildHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); }

2.调用startNestedScroll()方法开始嵌套滑动,并设置滑动方向:

case MotionEvent.ACTION_DOWN: { mDownX = x; mDownY = y; //通知父View开始嵌套滑动,并设置滑动方向(水平竖直方向都支持) startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL); break; }

这时候父view的onStartNestedScroll方法将会被回调,返回true表示允许此次嵌套滑动:

@Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return true; }

3.view开始滑动之前,会调用dispatchNestedPreScroll方法确定父view是否需要滑动。如果父view需要滑动,会消耗的距离放在consumed中,返回给子view,子view根据父view消耗的距离重新计算自己需要滑动的距离,进行滑动;如果父view不需要滑动,则子View自身处理滑动事件:

case MotionEvent.ACTION_MOVE: { int dx = x - mDownX; int dy = y - mDownY; //如果父View处理滑动事件 if (dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)) { //减去父View消耗的距离 dx -= consumed[0]; dy -= consumed[1]; } offsetLeftAndRight(dx); offsetTopAndBottom(dy); break; }

这时候父view的onNestedPreScroll方法将会被回调,协同处理滑动事件:

@Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(target, dx, dy, consumed); //向右滑动 if (dx > 0) { //滑动到边界 if (target.getRight() + dx > getWidth()) { dx = target.getRight() + dx - getWidth(); //父View消耗 offsetLeftAndRight(dx); consumed[0] += dx; } } //向左滑动 else { if (target.getLeft() + dx < 0) { dx = dx + target.getLeft(); //父View消耗 offsetLeftAndRight(dx); consumed[0] += dx; } } //向下滑动 if (dy > 0) { if (target.getBottom() + dy > getHeight()) { dy = target.getBottom() + dy - getHeight(); //父View消耗 offsetTopAndBottom(dy); consumed[1] += dy; } } //向上滑动 else { if (target.getTop() + dy < 0) { dy = dy + target.getTop(); //父View消耗 offsetTopAndBottom(dy); consumed[1] += dy; } } }

4.子view计算完自己的滑动距离进行滑动之后,调用dispatchNestedScroll方法进行滑动:

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return nestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); }

5.如果需要停止嵌套滑动,子view调用stopNestedScroll方法,父view的onStopNestedScroll方法被回调结束滑动:

case MotionEvent.ACTION_UP: { //结束嵌套滑动 stopNestedScroll(); break; }

至此,我们已经经历了一次完整的嵌套滑动流程,实际上内部都是通过NestedScrollingChildHelper实现的,我们只需要在恰当的地方传入参数调用方法即可。

关于NestedScrollingParentHelper源码解析可以参考下面的博客:

NestedScrollingParent,NestedScrollingParentHelper 详解

希望能对你有所帮助,源码已经同步上传到github上:

https://github.com/18722527635/AndroidArtStudy

欢迎star,fork,提issues,一起进步,下一篇再见~


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表