从源码角度分析NestedScrolling

简介: 通过CoordinatorLayout可以实现许多炫酷的效果,大家可以参考我之前一篇博客: 一起玩转CoordinatorLayout其实CoordinatorLayout就是利用NestedScrolling(嵌套滑动机制)来完成复杂的滑动交互。

通过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,一起进步,下一篇再见~

目录
相关文章
|
5天前
|
弹性计算 人工智能 安全
云上十五年——「弹性计算十五周年」系列客户故事(第二期)
阿里云弹性计算十五年深耕,以第九代ECS g9i实例引领算力革新。携手海尔三翼鸟、小鹏汽车、微帧科技等企业,实现性能跃升与成本优化,赋能AI、物联网、智能驾驶等前沿场景,共绘云端增长新图景。
|
11天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
|
2天前
|
云安全 人工智能 安全
Dify平台集成阿里云AI安全护栏,构建AI Runtime安全防线
阿里云 AI 安全护栏加入Dify平台,打造可信赖的 AI
|
10天前
|
人工智能 自然语言处理 自动驾驶
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
|
5天前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
483 13
|
4天前
|
编解码 文字识别 算法
一张图能装下“千言万语”?DeepSeek-OCR 用视觉压缩长文本,效率提升10倍!
一张图能装下“千言万语”?DeepSeek-OCR 用视觉压缩长文本,效率提升10倍!
378 10
|
10天前
|
编解码 自然语言处理 文字识别
Qwen3-VL再添丁!4B/8B Dense模型开源,更轻量,仍强大
凌晨,Qwen3-VL系列再添新成员——Dense架构的Qwen3-VL-8B、Qwen3-VL-4B 模型,本地部署友好,并完整保留了Qwen3-VL的全部表现,评测指标表现优秀。
682 7
Qwen3-VL再添丁!4B/8B Dense模型开源,更轻量,仍强大
|
13天前
|
机器学习/深度学习 缓存 自然语言处理
【万字长文】大模型训练推理和性能优化算法总结和实践
我们是阿里云公共云 AI 汽车行业大模型技术团队,致力于通过专业的全栈 AI 技术推动 AI 的落地应用。
599 37
【万字长文】大模型训练推理和性能优化算法总结和实践