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

目录
一、项目实例--电商首页
二、CoordinatorLayout 和 AppbarLayout 联动原理

刚复习完View事件分发、滑动冲突--《Android开发艺术探索》阅读笔记——第三章part2,接着想起前段时间项目中首页重构,遇到的嵌套滑动问题,以及CoordinatorLayout 和 AppbarLayout 联动原理。去学习了下先关知识。在此记录一下,备忘~
学习嵌套滑动的相关文章
自定义View事件之进阶篇(一)-NestedScrolling(嵌套滑动)机制
Android NestedScrolling机制完全解析 带你玩转嵌套滑动
十分钟Android中的嵌套滚动机制
一、项目实例--电商首页 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">

看到我们在 外部RecyclerView加上了parent--NestedScrollLayout2,NestedScrollLayout2是实现NestedScrollingParent2接口的。
而,RecyclerView是实现NestedScrollingChild2的。所以,RecyclerView及内部RecyclerView的滑动,都会先询问NestedScrollLayout2。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2

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等方法。)

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

    推荐阅读