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);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-MnDPgfdW-1713397601742)]

【算法合集】

[外链图片转存中…(img-uQCv5ukN-1713397601743)]

【延伸Android必备知识点】

[外链图片转存中…(img-l179Pnj8-1713397601743)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值