NestedScrollView嵌套滑动源码解读

类中如下变量:

private ViewParent mNestedScrollingParentTouch; // touch事件接力的父容器

private ViewParent mNestedScrollingParentNonTouch; // 非touch事件接力的父容器

private final View mView; // 当前容器,也是作为嵌套滑动时孩子角色的容器

private boolean mIsNestedScrollingEnabled; // 当前容器是否支持嵌套滑动

private int[] mTempNestedScrollConsumed; // 二维数组,保存x、y消耗的事件长度;减少对象生成的

复制代码

2.1 实例获取

public NestedScrollingChildHelper(@NonNull View view) {

mView = view;

}

复制代码

2.2 嵌套滑动支持

是对嵌套子视图的角色来说的

public void setNestedScrollingEnabled(boolean enabled) {

if (mIsNestedScrollingEnabled) {

ViewCompat.stopNestedScroll(mView); // 兼容模式调用

}

mIsNestedScrollingEnabled = enabled;

}

public boolean isNestedScrollingEnabled() {

return mIsNestedScrollingEnabled;

}

复制代码

2.3 嵌套滑动相关方法

要支持嵌套滑动,那么必须有多个支持嵌套滑动的容器;作为子视图,其需要有通知的一套,因此方法有:

  • 父容器的查找、判断

  • 通知开始、过程以及结束

2.3.1 嵌套父容器的查找

成员变量mNestedScrollingParentTouch、mNestedScrollingParentNonTouch为父容器缓存变量;其直接设置和获取方法如下

private ViewParent getNestedScrollingParentForType(@NestedScrollType int type) {

switch (type) {

case TYPE_TOUCH:

return mNestedScrollingParentTouch;

case TYPE_NON_TOUCH:

return mNestedScrollingParentNonTouch;

}

return null;

}

private void setNestedScrollingParentForType(@NestedScrollType int type, ViewParent p) {

switch (type) {

case TYPE_TOUCH:

mNestedScrollingParentTouch = p;

break;

case TYPE_NON_TOUCH:

mNestedScrollingParentNonTouch = p;

break;

}

}

复制代码

2.3.2 嵌套父容器的支持判断

public boolean hasNestedScrollingParent() {

return hasNestedScrollingParent(TYPE_TOUCH);

}

public boolean hasNestedScrollingParent(@NestedScrollType int type) {

return getNestedScrollingParentForType(type) != null;

}

复制代码

2.3.3 滑动开始通知

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {

if (hasNestedScrollingParent(type)) {

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;

}

复制代码

父容器的查找,采取了延时策略,在进行事件时,才进行查询,并且在查询到了,进行支持;所以可以这样理解:

  1. onStartNestedScroll:是父容器接受事件通知方法,其结果表示是否可以作为嵌套滑动的父容器角色

  2. onNestedScrollAccepted:不是必调用,调用了表明嵌套父容器角色支持view的后续嵌套处理

2.3.4 手指滑动通知

滑动时通知,分为滑动前和滑动后;使嵌套滑动处理更灵活 滑动前通知

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,

@Nullable int[] offsetInWindow) {

return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH);

}

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,

@Nullable int[] offsetInWindow, @NestedScrollType int type) {

if (isNestedScrollingEnabled()) {

final ViewParent parent = getNestedScrollingParentForType(type);

if (parent == null) {

return false;

}

if (dx != 0 || dy != 0) {

int startX = 0;

int startY = 0;

if (offsetInWindow != null) {

mView.getLocationInWindow(offsetInWindow);

startX = offsetInWindow[0];

startY = offsetInWindow[1];

}

if (consumed == null) {

consumed = getTempNestedScrollConsumed();

}

consumed[0] = 0;

consumed[1] = 0;

ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);

if (offsetInWindow != null) {

mView.getLocationInWindow(offsetInWindow);

offsetInWindow[0] -= startX;

offsetInWindow[1] -= startY;

}

return consumed[0] != 0 || consumed[1] != 0;

} else if (offsetInWindow != null) {

offsetInWindow[0] = 0;

offsetInWindow[1] = 0;

}

}

return false;

}

复制代码

其中两个二维数组作为结果回传;通过父容器的onNestedPreScroll方法进行处理并把滑动处理详情放入两个二维数组中,常用的详情为消耗长度情况;返回结果表示滑动前是否处理

滑动后通知

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,

int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {

return dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,

offsetInWindow, TYPE_TOUCH, null);

}

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,

int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) {

return dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,

offsetInWindow, type, null);

}

public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,

int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type,

@Nullable int[] consumed) {

dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,

offsetInWindow, type, consumed);

}

private boolean dispatchNestedScrollInternal(int dxConsumed, int dyConsumed,

int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,

@NestedScrollType int type, @Nullable int[] consumed) {

if (isNestedScrollingEnabled()) {

final ViewParent parent = getNestedScrollingParentForType(type);

if (parent == null) {

return false;

}

if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {

int startX = 0;

int startY = 0;

if (offsetInWindow != null) {

mView.getLocationInWindow(offsetInWindow);

startX = offsetInWindow[0];

startY = offsetInWindow[1];

}

if (consumed == null) {

consumed = getTempNestedScrollConsumed();

consumed[0] = 0;

consumed[1] = 0;

}

ViewParentCompat.onNestedScroll(parent, mView,

dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);

if (offsetInWindow != null) {

mView.getLocationInWindow(offsetInWindow);

offsetInWindow[0] -= startX;

offsetInWindow[1] -= startY;

}

return true;

} else if (offsetInWindow != null) {

offsetInWindow[0] = 0;

offsetInWindow[1] = 0;

}

}

return false;

}

复制代码

其中两个二维数组作为结果回传;通过父容器的onNestedScroll方法进行处理并把滑动处理详情放入两个二维数组中,常用的详情为消耗长度情况;返回结果表示滑动前是否处理

2.3.5 滑翔通知

滑翔也有两个时机

滑翔前

public boolean dispatchNestedPreFling(float velocityX, float velocityY) {

if (isNestedScrollingEnabled()) {

ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH);

if (parent != null) {

return ViewParentCompat.onNestedPreFling(parent, mView, velocityX,

velocityY);

}

}

return false;

}

复制代码

返回结果表明父容器的是否处理滑翔;父容器是通过onNestedPreFling进行处理

滑翔后

public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {

if (isNestedScrollingEnabled()) {

ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH);

if (parent != null) {

return ViewParentCompat.onNestedFling(parent, mView, velocityX,

velocityY, consumed);

}

}

return false;

}

复制代码

返回结果表明父容器的是否处理滑翔;父容器是通过onNestedFling进行处理

滑翔是一个互斥处理的过程,而滑动是一个接力的过程

2.3.6 滑动结束通知

public void stopNestedScroll() {

stopNestedScroll(TYPE_TOUCH);

}

public void stopNestedScroll(@NestedScrollType int type) {

ViewParent parent = getNestedScrollingParentForType(type);

if (parent != null) {

// 通知嵌套父容器,滑动结束

ViewParentCompat.onStopNestedScroll(parent, mView, type);

setNestedScrollingParentForType(type, null); // 清理父容器引用

}

}

复制代码

3、NestedScrollingParentHelper类


作为嵌套滑动的父容器角色,其只有接受通知时处理即可,情况没有子视图角色那么复杂;而辅助类里仅仅是对滑动方向做了声明周期处理;

成员变量

private int mNestedScrollAxesTouch; // Touch事件时,接受处理时,事件的滑动方法

private int mNestedScrollAxesNonTouch; // 非Touch事件时,接受处理时,事件的滑动方法

复制代码

3.1 滑动方向获取

public int getNestedScrollAxes() {

return mNestedScrollAxesTouch | mNestedScrollAxesNonTouch;

}

复制代码

3.2 滑动方向设置

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,

@ScrollAxis int axes) {

onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH);

}

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,

@ScrollAxis int axes, @NestedScrollType int type) {

if (type == ViewCompat.TYPE_NON_TOUCH) {

mNestedScrollAxesNonTouch = axes;

} else {

mNestedScrollAxesTouch = axes;

}

}

复制代码

3.3 滑动方向重置

public void onStopNestedScroll(@NonNull View target) {

onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);

}

public void onStopNestedScroll(@NonNull View target, @NestedScrollType int type) {

if (type == ViewCompat.TYPE_NON_TOUCH) {

mNestedScrollAxesNonTouch = ViewGroup.SCROLL_AXIS_NONE;

} else {

mNestedScrollAxesTouch = ViewGroup.SCROLL_AXIS_NONE;

}

}

复制代码

4、嵌套实现机制


作为一是具有兼容性实现的嵌套滑动容器,它必须实现下面接口

  • 滑动容器接口ScrollingView

  • 嵌套滑动父容器接口NestedScrollingParent3

  • 嵌套滑动子视图接口NestedScrollingChild3

嵌套接口,可以根据容器角色选择实现;方法实现需要利用辅助类

从上面对两个辅助类解读;对他们已经实现的功能做了归纳

  1. 嵌套是否支持

  2. 嵌套通知

  3. 嵌套滑动方向

也就是作为子视图角色的实现方法基本使用辅助类即可,而嵌套父容器角色需要我们增加实现逻辑;需要实现从功能上划分:

  1. 作为嵌套子视图设置,

  2. 作为嵌套父容器的实现

  3. 滑动接力处理,以及滑翔处理

4.1 嵌套子视图支持

构造器中进行setNestedScrollingEnabled(true)方法进行设置

setNestedScrollingEnabled方法

public void setNestedScrollingEnabled(boolean enabled) {

mChildHelper.setNestedScrollingEnabled(enabled);

}

复制代码

4.2 嵌套父容器的支持

public boolean onStartNestedScroll(

@NonNull View child, @NonNull View target, int nestedScrollAxes) {

return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

【Android高级架构视频学习资源】

Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
点击传送门,即可获取!

l View target, int nestedScrollAxes) {

return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-5RpeklLx-1715164435493)]

【Android高级架构视频学习资源】

Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
点击传送门,即可获取!

  • 29
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值