遇到的问题
最近在需求中遇到了一个问题,先贴出大致布局代码:
|
|
viewPager里是Fragment,Fragment的布局就是一个Recyclerview。到这里的布局都与CoordinatorLayout协作很好,但是,有点小小的需求就是,当数据为空的空提示布局和网络错误的网络错误布局出现的时候,并不能与CoordinatorLayout协作。换句话说,就是当点击并试图拖动上拉的区域是空布局或者是错误布局时,无法拖动,也无法将AppbarLayout隐藏。
原因是啥? 因为空布局不像recyclerview一样实现Nested相关接口,所以无法和CoordinatorLayout协作啊。
解决方法是啥?写个实现Nested相关接口的布局,将正常态,空态,异常态三种状态包裹进来不就完啦!
于是,话题展开:如何写一个支持CoordinatorLayout和Nested的FrameLayout。
需求分析
这个中间层的FrameLayout要做的目标比较明确:
- 当子view实现Nested相关接口时,直接把子view的各种事件传到上层。
- 当子view并没有实现相关接口时,自己处理。
分析结果: 这个FrameLayout需要继承NestedScrollingChild, NestedScrollingParent这俩接口。
写前准备
先来说说Nested相关的一些知识:
NestedScrolling 提供了一套父 View 和子 View 滑动交互机制。要完成这样的交互,父 View 需要实现 NestedScrollingParent 接口,而子 View 需要实现 NestedScrollingChild 接口。
|
|
像recyclerview就实现了NestedScrollingChild接口,CoordinatorLayout 类实现了NestedScrollingParent接口。
NestedScrollingChild类
|
|
首先来说 NestedScrollingChild。如果你有一个可以滑动的 View,需要被用来作为嵌入滑动的子 View,就必须实现本接口。在此 View 中,包含一个 NestedScrollingChildHelper 辅助类。NestedScrollingChild 接口的实现,基本上就是调用本 Helper 类的对应的函数即可,因为 Helper 类中已经实现好了 Child 和 Parent 交互的逻辑。原来的 View 的处理 Touch 事件,并实现滑动的逻辑大体上不需要改变。
需要做的就是,如果要准备开始滑动了,child需要告诉 Parent:要准备进入滑动状态了。调用 startNestedScroll()。
child在滑动之前,先问一下 Parent 是否需要滑动,也就是调用 dispatchNestedPreScroll()。
如果 Parent 滑动了一定距离,child 需要重新计算一下 Parent 滑动后剩下给child的滑动距离余量。
然后,child进行余下的滑动。
最后,如果滑动距离还有剩余,child 就再问一下,Parent 是否需要在继续滑动你剩下的距离,也就是调用 dispatchNestedScroll()。
NestedScrollingParent类
|
|
作为一个可以嵌入 NestedScrollingChild 的父 View,需要实现 NestedScrollingParent,这个接口方法和 NestedScrollingChild 大致有一一对应的关系。同样,也有一个 NestedScrollingParentHelper 辅助类来帮助你实现和 Child 交互的逻辑。滑动动作是 Child 主动发起,Parent 就收滑动回调并作出响应。
从上面的 Child 分析可知,滑动开始的调用 startNestedScroll(),Parent 收到 onStartNestedScroll() 回调,决定是否需要配合 Child 一起进行处理滑动,如果需要配合,还会回调 onNestedScrollAccepted()。
每次滑动前,Child 先询问 Parent 是否需要滑动,即 dispatchNestedPreScroll(),这就回调到 Parent 的 onNestedPreScroll(),Parent 可以在这个回调中“劫持”掉 Child 的滑动,也就是先于 Child 滑动。
Child 滑动以后,会调用 onNestedScroll(),回调到 Parent 的 onNestedScroll(),这里就是 Child 滑动后,剩下的给 Parent 处理,也就是 后于 Child 滑动。
最后,滑动结束,调用 onStopNestedScroll() 表示本次处理结束。
用个表格 言简意赅:
| child | parent |
|---|---|
| startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
| dispatchNestedPreScroll | onNestedPreScroll |
| dispatchNestedScroll | onNestedScroll |
| stopNestedScroll | onStopNestedScroll |
大概原理就这样,不再多说。
如何解决?
作为Parent该如何处理?
在实现 NestedScrollingParent后我们要重写以下方法:
|
|
作为parent时,我们的需求是将 支持Nested的 child 滑动事件上传。
那么,在onStartNestedScroll中,直接返回 true,告诉child 可以协作;
在onNestedScrollAccepted中,调用mParentHelper.onNestedScrollAccepted后,再调用该framelayout作为child的方法:startNestedScroll,通知更高层的 parent;
在onNestedScroll中,直接把其child的消耗传上去;
在onNestedPreScroll也是如此,自己不作处理。Fling相关也类似。
作为child如何处理
在实现NestedScrollingChild接口,我们要重写如下方法,其实都是调用mChildHelper帮助类。
|
|
由于我们的FrameLayout并没有滚动的功能,在实现child接口时,就没那么复杂了。
|
|
主要是在onTouchEvent中,在MotionEvent.ACTION_MOVE时,分别调用dispatchNestedPreScroll dispatchNestedScroll 这两个函数, 在往上滑,并且AppbarLayout没有收起时,dispatchNestedPreScroll会返回true,否则会走到dispatchNestedScroll。
完整代码如下:
|
|
就这么多吧~