Android 嵌套滑动解决

NestedScrollingParent与NestedScrollingChild

1、 嵌套滑动的解决方案先看注释了解方法

这时Google官方给的处理方案,在Androidx或者support包中

public interface NestedScrollingChild {
  
    //设置是否允许嵌套滑动,允许的话设为true
    void setNestedScrollingEnabled(boolean enabled);
 
    //是否允许嵌套滑动
    boolean isNestedScrollingEnabled();

    //开始嵌套滑动 axes:滑动方向,一般用于通知父控件我要看是滑动了
    boolean startNestedScroll(@ScrollAxis int axes);
 
    //停止嵌套滑动,一般也用于通知父控件
    void stopNestedScroll();

    // 判断父控件NestedScrollingParent 的onStartNestedScroll方法是否返回true,
    //只有true,才能继续一系列的嵌套滑动
    boolean hasNestedScrollingParent();

    /**
     * 子控件消费了一部分滑动之后通知父控件滑动
     * @param dxConsumed    子控件X方向已消耗长度
     * @param dyConsumed    子控件Y方向已消耗长度
     * @param dxUnconsumed  子控件X方向未消耗长度
     * @param dyUnconsumed  子控件Y方向未消耗长度
     * @param offsetInWindow  控件在window上的偏移
     * @return 数据是否可以分发
     */
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);

    /**
     * 子控件滑动之前通知父控件滑动
     * @param dx    x方向上触摸滑动距离
     * @param dy    y方向上触摸滑动距离
     * @param consumed  父控件滑动后未消耗的距离,是父控件返回的值,不是子控件的值
     *                   consumed[0]-X  consumed[1]为Y 
     * @param offsetInWindow  控件在window上的偏移
     * @return  数据是否可以分发
     */
    boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow);

    //子控件消耗Fling滑动后通知父控件,velocityX每秒滑动速度,
    boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);

    //子控件消耗Fling滑动之前通知父控件fling滑动
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
public interface NestedScrollingParent {
    //子控件开始嵌套滑动通知了父控件,这个方法接收调用,父控件决定是否嵌套滑动 @return 是否嵌套滑动
    boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
 
    //子控件开始嵌套滑动通知了父控件,这个方法也会接收调用,
    void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);

    //子控件停止嵌套滑动通知父控件
    void onStopNestedScroll(@NonNull View target);

    //子控件调用 dispatchNestedScroll 父控件这个方法接收调用
    void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);

    //子控件调用 dispatchNestedPreScroll 父控件这个方法接收调用 consumed需要父控件赋值
    void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
 
     //子控件调用 dispatchNestedFling 父控件这个方法接收调用,惯性滑动
    boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);

    //子控件调用 dispatchNestedPreFling 父控件这个方法接收调用,惯性滑动
    boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);

    //获取当前父控件的滑动方向
    int getNestedScrollAxes();
}

了解Android事件分发的可以思考一下,事件分发是父控件分发给子控件,父控件也是dispath方法分发,子控件on方法接收,这里其实就是反过来,子控件dispath分发,父控件接收处理,处理结果有返回给子控件。

2、嵌套滑动调用流程

子view 父View
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll

子控件开始滑动调用startNestedScroll通知父控件我要看是滑动了,父控件就会被调用onStartNestedScroll、onNestedScrollAccepted,并返回给子控件我要不要滑动,如果父控件不滑动到此结束,子控件自己滑动就行了,
父控件需要滑动,则子控件开始决定怎样配合滑动,

  1. 子控件先滑动父控件后滑动,子控件滑动后,调用dispatchNestedScroll,父控件onNestedScroll被调用,如果有父控件没消费的一部分滑动由参数dxUnconsumed返回给子控件继续滑动,
  2. 父控件先滑动子控件后滑动,子控件先调用dispatchNestedPreScroll,父控件则onNestedPreScroll被调用,父控件滑动后将没有消耗的滑动赋值给consumed,交给子控件,子控件开始滑动,
    3:dispatchNestedFling与dispatchNestedPreFling一样逻辑

3、示例解析

示例源码

3.1 自定义NestedScrollChildLayout和NestedScrollParentLayout子父控件

只要实现NestedScrollingChild 和 NestedScrollingParent接口即可,实现所有方法。

3.2 NestedScrollingChildHelper与NestedScrollingParentHelper

通过名字可以看出这是子控件和父控件嵌套滑动各个控件的帮助类,他们的方法几乎都对应着两个嵌套滑动接口。在androidx或support包中。

  1. NestedScrollingChildHelper: 子控件调用,寻找父控件并调用父控件中的NestedScrollingParent方法。
  2. NestedScrollingParentHelper:父控件调用,寻找子控件调用子控件的NestedScrollingChild 方法。

3.3 子控件处理触摸滑动事件实现嵌套滑动

触摸滑动处理方法 handleMoveY(int distanceY), 这里只处理上下滑动

    private void handleMoveY(int distanceY){
        //父控件是否允许滑动
        if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)) {
            if (distanceY > 0) {
                //向上滑, 父控件先滑动子控件再滑动
                if (dispatchNestedPreScroll(0,   distanceY, consumed, offset)) {
                    //consumed为父控件消耗的距离, 未消耗的子控件继续滑动
                    int unConsumed =   distanceY - consumed[1];
                    if (unConsumed != 0) {
                        scrollBy(0, unConsumed);
                    }
                } else {
                    scrollBy(0,   distanceY);
                }
            } else {
                //向下滑, 子控件先滑动父控件后滑动
                if (getScrollY() >= 0) {
                    if (getScrollY() == 0) {
                        //子控件已不再需要滑动,父控件滑动
                        dispatchNestedScroll(0, 0, 0, distanceY, offset);
                        return ;
                    }
                    //子控件可以消耗所有滑动,先滑动自己
                    if (getScrollY() +   distanceY >= 0) {
                        scrollBy(0,   distanceY);
                    } else if (getScrollY() != 0) {
                        //子控件滑动一部分,剩余给父控件滑动
                        int consume = getScrollY();
                        int unConsumed = (int) distanceY + consume;
                        scrollTo(0, -consume);  //先自己滑动
                        dispatchNestedScroll(0, consume, 0, unConsumed, offset);
                    } else {
                        dispatchNestedScroll(0, 0, 0,   distanceY, offset);
                    }
                } else {
                    scrollTo(0, 0);
                }
            }
        }
    }

由上面注释可以看出,嵌套滑动完全按照 2、嵌套滑动调用流程来完成,

  1. 向上滑,父控件先消耗一定的滑动距离,子控件滑动剩余距离。父控件则隐藏第一个控件。
  2. 向下滑,子控件先滑动到顶部,之后将剩余的滑动距离或后续的滑动分发给父控件。父控件显示出第一个控件。

3.4 父控件处理子控件分发的滑动

父控件需要的滑动是隐藏或显示第一个控件,则父控件可滑动的距离就是第一个控件的高度

    //向下滑动,子控件已滑动顶部,分发剩余滑动与后续的滑动,父控件开始滑入第一个控件。
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        Log.i(TAG, "onNestedScroll: dyConsumed=" + dxConsumed + "  dyUnconsumed=" + dyUnconsumed);
        //父控件后滑动为显示上部控件
        if (getScrollY() > 0 && dyUnconsumed < 0) {
            if (getScrollY() + dyUnconsumed >0) {
                scrollBy(0, dyUnconsumed);
            } else {
                scrollTo(0, 0);
            }
        }
    }

    //向上滑动,父控件先滑动,将已消耗的滑动距离传给子控件,子控件继续滑动,实现父控件第一个控件滑出屏幕。
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(target, dx, dy, consumed);
        Log.i(TAG, "onNestedPreScroll: dx=" + dx + " dy=" + dy);
        View view = getChildAt(0);
        //父控件先滑动为隐藏上部控件,
        if (getScrollY() < view.getHeight() && dy > 0) {
            if (getScrollY() + dy <= view.getHeight()) {
                scrollBy(0, dy);
                consumed[1] = dy;
            } else {
                consumed[1] = view.getHeight() - getScrollY();
                scrollTo(0, view.getHeight());
            }
        }
    }

4. 接口系统化

在Android api 21之后,嵌套滑动的的接口已经系统化,Android原生系统view与ViewGroup就自带了嵌套滑动的所有接口方法,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,930评论 1 332
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,642评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,928评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,639评论 0 193
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,806评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,698评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,139评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,864评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,485评论 0 230
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,955评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,355评论 1 243
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,811评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,300评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,840评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,464评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,577评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,628评论 2 249

推荐阅读更多精彩内容