嵌套滑动--NestedScroll-项目实例(淘宝首页缺陷),及CoordinatorLayout 和 AppbarLayout 联动原理

代码Github地址,欢迎star和issue

刚复习完View事件分发、滑动冲突--《Android开发艺术探索》阅读笔记——第三章part2,接着想起前段时间项目中首页重构,遇到的嵌套滑动问题,以及CoordinatorLayout 和 AppbarLayout 联动原理。去学习了下先关知识。在此记录一下,备忘~

学习嵌套滑动的相关文章:
自定义View事件之进阶篇(一)-NestedScrolling(嵌套滑动)机制.
Android NestedScrolling机制完全解析 带你玩转嵌套滑动

一、项目实例--电商首页

1、嵌套滑动的问题点

看懂了以上文章后,现在来分享一下项目中的问题。因为公司项目同为电商,也恰好看了淘宝、京东的首页,就拿它俩举例吧。

通常首页都是一个RecyclerView,然后底部是Tab+frangment(内部recyclerview)组成的瀑布流商品---- 一起作为外部RecyclerView的最后一个item,很多电商都是这样。分别看下淘宝、京东的 外部RecyclerView(整个首页列表)、内部RecyclerView(底部tab中的商品流列表) 嵌套时的滑动效果。

在这里插入图片描述
京东 VS 淘宝
在这里插入图片描述

可以清楚看到

京东:滑动很顺畅,没有停滞的情况,tab到顶部后就 紧接着 滑动内部商品列表了。整个过程手指是连续拖动的,没有抬起。

淘宝:在tab滑到顶部后,手指继续拖动,但商品流是不能滑动的。这时手指抬起然后再次拖动商品流 才会滑动。

很显然,我们认为京东的滑动更丝滑。那为啥淘宝会出现这个情况呢?

2、缺陷原因分析

==原因分析==:从view事件分发机制 我们知道,当parent View拦截事件后,那同一事件序列的事件会直接都给parent处理,子view不会接受事件了所以 按照正常处理滑动冲突的思路处理----当tab没到顶部时,parent拦截事件,tab到顶部时 parent就不拦截事件,但是由于手指没抬起来,所以这一事件序列还是继续给parent,不会到内部RecyclerView,所以商品流就不会滑动了。 (这里不清楚的可以参考View事件分发、滑动冲突--《Android开发艺术探索》阅读笔记——第三章part2

==解决方案==使用嵌套滑动,具体如下。

1、添加嵌套滑动父布局

<*.NestedScrollLayout2
            android:id="@+id/nest_scroll_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/orv_main_page"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

        </NestedScrollLayout2>

外部RecyclerView加上了父布局,NestedScrollLayout2,NestedScrollLayout2是实现==NestedScrollingParent2==接口的,这个很重要。

2、嵌套滑动父布局的实现原理

NestedScrollLayout2 extends FrameLayout implements NestedScrollingParent2

上面说了,实现==NestedScrollingParent2==接口很重要,目的就是 在 开始滑动 外部RecyclerView 时、开始滑动内部RecyclerView时,都询问NestedScrollLayout2是否处理且如何处理。

所以,根据我们的问题,在向上滑动内部RecyclerView时,如果tab没到顶就让parent消费事件,且滑动外部RecyclerView;到顶了,就滑内部RecyclerView。 相对的, 向下滑动内部RecyclerView时,如果还能滑就滑内部RecyclerView;如果已经滑到顶部就让parent去滑动外部RecyclerView。

在滑外部外部RecyclerView时,也是一样逻辑。 具体看代码,这里贴NestedScrollLayout2关键代码,有注释说明,就是对上面文字的代码实现而已。 其中mRootList是外部RecyclerView,mChildList是内部RecyclerView,childTop是tab这个view的top 用于判断是否到顶部。scrollListener是监听tab到顶部后设置其背景色用的。主要关注调用scrollBy时滚动的是哪个列表,滚动了多少。
(如果阅读这段理解不清晰,建议再去看上面提的嵌套滑动文章)

public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
            int type) {
        if (mChildView != null) {
            //target就是接受到事件的child,这里就是parent接受嵌套滑动后的pre处理
            if (target == mRootList) {
                //滑外部列表
                onParentScrolling(mChildView.getTop(), dy, consumed);
            } else {
                //滑内部列表
                onChildScrolling(mChildView.getTop(), dy, consumed);
            }
        }
    }


    /**
     * 父列表在滑动
     */
    private void onParentScrolling(int childTop, int dy, int[] consumed) {
        //列表已经置顶
        if (childTop == 0) {
            if (!isTabsTop) {
                isTabsTop = true;
                if (scrollListener != null) {
                    scrollListener.onTabsStateChanged(isTabsTop, mChildView);
                }
            }
            if (dy > 0 && mChildList != null) {
                consumed[1] = dy;
                if (!mChildList.canScrollVertically(dy)) {
                    //正在loading的时候不要响应上滑事件
                    RecyclerView.Adapter adapter = mChildList.getAdapter();
                    if (adapter instanceof BaseQuickAdapter) {
                        BaseQuickAdapter quickAdapter = (BaseQuickAdapter) adapter;
                        if (quickAdapter.isLoading()) {
                            mRootList.stopScroll();
                        }
                    }
                    if (!isBottom) {
                        isBottom = true;
                        if (scrollListener != null) {
                            scrollListener.onReachBottom(mChildList, mChildView);
                        }
                    }
                } else {
                    //还在向下滑动,此时滑动子列表
                    scrollBy(dy, mChildList);
                }
            } else {
                if (mChildList != null && mChildList.canScrollVertically(dy)) {
                    consumed[1] = dy;
                    scrollBy(dy, mChildList);
                }
            }
        } else {
            if (childTop < dy) {
                //tab没有置顶,parent就消耗下面这个差值,所以父列表就只能滑动childTop了。到顶后,就是上面的逻辑了。
                //childTop是tab到顶部的距离。
                consumed[1] = dy - childTop;
            }
            if (isTabsTop) {
                isTabsTop = false;
                if (scrollListener != null) {
                    scrollListener.onTabsStateChanged(isTabsTop, mChildView);
                }
            }
        }
    }

    /**
     * 滑动
     *
     * @param dy 滑动距离
     */
    private void scrollBy(int dy, RecyclerView recyclerView) {
        try {
            recyclerView.scrollBy(0, dy);
        } catch (Exception e) {
            ExceptionReporterHelper.reportException(e);
        }
    }

    /**
     * 内部列表 接受事件的处理
     */
    private void onChildScrolling(int childTop, int dy, int[] consumed) {
        if (childTop == 0) {
            if (dy < 0) {
                //tab在顶,向下滑动,如果子列表不能滑了,parent把dy都消耗掉,然后滑外部列表。
                if (!mChildList.canScrollVertically(dy)) {
                    consumed[1] = dy;
                    scrollBy(dy, mRootList);
                }
            }
        } else {
            if (dy < 0 || childTop > dy) {
                consumed[1] = dy;
                scrollBy(dy, mRootList);
            } else {
                //dy大于0
                consumed[1] = dy;
                scrollBy(childTop, mRootList);
            }
        }
    }

二、CoordinatorLayout 和 AppbarLayout 联动原理

我以前分享过CoordinatorLayout的使用:《Android进阶之光》Design Support Library常用控件(二):CoordinatorLayout,只懂基本的使用(其实平时开发够用了)。看了下面这两篇才是略懂原理。

《AppBarLayout滑动原理》

总结一:AppBarLayout滑动原理,手指滑动AppBarLayout时,滑动appBarlayout时,本身及内部子view不消费事件,然后事件走到CoordinatorLayout的OnTouchEvent中,接着遍历子view的behavior,因为appbarLayout通过注解添加的behavior实现了CoordinatorLayout.Behavior中定义的onStartNestedScroll/onNestedPreScroll等方法,所以appbarLayout可以通过behavior这些方法进行滑动内部子view。

《CoordinatorLayout 和 AppbarLayout 联动原理解析 》

总结二:联动原理,手指滑动recyclerView时,由于和CoordinatorLayout形成前套滑动,所以事件交给CoordinatorLayout处理,在CoordinatorLayout的OnTouchEvent中,处理方式就是总结一了,即交给AppBarLayout滑动了。那recyclerView此时也会跟着滑动,为啥呢?是因为recyclerView设置的behavior(“app:layout_behavior="@string/appbar_scrolling_view_behavior”),这个behavior的作用就纯粹为了让 recyclerView一直保持在AppBarLayout下方。(这个behavior没有实现onStartNestedScroll/onNestedPreScroll等方法。)

代码Github地址,欢迎star和issue

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

推荐阅读更多精彩内容