效果需求:顶栏在列表下滑的时候先行滑出,上滑时先行滑入
看到后快速实现的想法集中在CoordinatorLayout
和AppbarLayout
上,只需要配置上app:layout_scrollFlags="scroll|enterAlways"
就能实现该效果。

简易效果有了然后把title替换成目标内容,开始出现违和感了。首先AppbarLayout
会为自己自动添加一个背景色,另一个问题是ScrollingViewBehavior
的实现是下拉的时候优先将AppbarLayout
和RecyclerView
一起向下移动,直到AppbarLayout
完全展示才将滑动事件交给RecyclerView
处理,上滑同理。在这个过程中,两个View一直是纵向上的上下关系,无法做到下图中的效果。

效果未达预期 自己动手写一个
因为CoordinatorLayout
帮我们做了很多事情,所以我们只需要用Behavior做滑动的处理就行了。
首先重写方法layoutDependsOn
确定需要依赖的View(如果只是处理滑动的话,这一步不是必须的,但是有另外的打算)
override fun layoutDependsOn(parent: CoordinatorLayout,child: View,dependency: View): Boolean {
//暂时先直接依赖RecyclerView,这里是可以随机应变的
return dependency is RecyclerView
}
然后重写方法onDependentViewChanged
。说一下在这里需要处理的事情:记录bar的高度,并设置RecyclerView
的translationY
。
题外话:为什么ScrollingViewBehavior
能直接在xml布局中展示出RecyclerView
在AppbarLayout
下方的效果?因为ScrollingViewBehavior
是作用于RecyclerView
依赖于AppbarLayout
,其中重写了方法onChildLayout
直接对RecyclerView
进行重新布局,而目前实现方案是Behavior作用于bar依赖于RecyclerView
,无法对RecyclerView
的位置进行重新布局。
override fun onDependentViewChanged(parent: CoordinatorLayout,child: View,dependency: View): Boolean {
//无法通过layout方法调整RecyclerView的位置,那么只能在这里做出调整
//因为该方法在绑定的时候必定会回调一次
if (floatViewHeight < 0) {
//获取bar的高度
floatViewHeight = (child.measuredHeight + child.marginVertical()).toFloat()
//关于fitsSystemWindows的处理
//减去view底部被系统填充的底部padding
// (虽然也会减去自己设置的padding,所以这个view尽量不要设置纵向的padding)
if (ViewCompat.getFitsSystemWindows(child)) floatViewHeight -= child.paddingBottom
//为RecyclerView设置偏移
dependency.translationY = floatViewHeight
return true
}
return false
}
剩下的就是在方法onNestedPreScroll
做滑动预处理,这个方法涉及到了NestedScrolling的机制,目前个人也只知道个大概,还无法进行灵活地使用。
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: View, target: View,dx: Int,dy: Int,consumed: IntArray,type: Int) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
//当view绑定的时候,recyclerview中不一定有数据,recyclerview不一定拥有高度
//所以需要在滑动的时候才能知道recyclerview的高度
//才能计算出活动范围
if (offsetLimit < 0) {
offsetLimit =
(target.measuredHeight + target.marginVertical() + floatViewHeight - coordinatorLayout.measuredHeight)
.coerceIn(0f, floatViewHeight)
}
//向上滑动
if (dy > 0) {
//判断bar能否移动
if (child.translationY > -offsetLimit) {
child.translationY = (child.translationY - dy).coerceIn(-offsetLimit, 0F)
}
//判断recyclerview是否需要移动
//向上滑动时,recyclerview需要先消耗完自己的translationY才能滑动列表
if (target.translationY > floatViewHeight - offsetLimit) {
target.translationY =
(target.translationY - dy).coerceIn(floatViewHeight - offsetLimit, floatViewHeight)
//这里其实是有误差的,因为在上一行中不一定消耗了dy,可能会比dy少,不过问题不大
consumed[1] = dy
}
}
//向下滑动
if (dy < 0) {
//判断bar能否移动
if (child.translationY < 0) {
child.translationY = (child.translationY - dy).coerceIn(-floatViewHeight, 0F)
}
//这里判断增加了一个条件,recyclerview需要无法下拉之后再进行移动
if (!target.canScrollVertically(-1) && target.translationY < floatViewHeight) {
target.translationY =
(target.translationY - dy).coerceIn(floatViewHeight - offsetLimit, floatViewHeight)
//这里同上,同样存在误差,不过问题不大
consumed[1] = dy
}
}
}
结语
整个behavior不是很复杂,核心代码全在上面,最终就能完成上面图二的效果。自己动手一遍基本上能了解behavior的用法,同时也了解到了CoordinatorLayout
与behavior的组合是个很强大而且很有意思的东西。最后希望自己能把自己造轮子这个系列写下去,这个系列不会提供完整的源码,但是会提到自己实现过程中的细节,目标是看完+动手,自己也能写。分享给屏幕前的你,记录给在未来的我。