CoordinatorLayout顾名思义协调布局,是用来协调该布局下的子控件,最简单地使用就是头部伸缩和折叠了,配合着TabLayout,只需要设置一下AppBarLayout子控件的layout_scrollFlags以及底下滑动控件的layout_behavior就行了,组合起来效果非常不错,网上这种详细的教程有很多,这片文章主要讲述一下简单的自定义behavior。
在behavior中可以设置触摸事件拦截onInterceptTouchEvent(),触摸事件响应onTouchEvent(),View依赖另一个ViewlayoutDependsOn()、onDependentViewChanged(),嵌套滑动事件处理onStartNestedScroll()、onStopNestedScroll()、onNestedScroll()、onNestedPreScroll()以及还有两个快速滑动的方法,
这篇文章使用的是behavior的依赖方法,ViewlayoutDependsOn()和onDependentViewChanged()。
ViewlayoutDependsOn(CoordinatorLayout parent, View child, View dependency):child是使用这个behavior的view,dependency则是child所依赖的view。何为依赖?在我看来,就是dependency改变的时候,child能根据dependency的变化,做出反应,如何反应?当 ViewlayoutDependsOn 返回true的时候就可以触发 onDependentViewChanged。
反应动作写在onDependentViewChanged(CoordinatorLayout parent, View child, View dependency),比方说,可以通过dependency.getX dependency.getY获取的坐标,设置给child,该View就会跟着依赖View动起来,或者让它反方向动,都可以,非常灵活,最后return true就会生效。
那么来尝试一下吧,UC中首页漂亮的交互动画非常的吸引人,自己也想学着做一个简单的页面。单独看这个页面的话,有三块布局,一个是底下的RecyclerView,一个是逐渐被遮住的导航栏,最后一个是在RecyclerView逐渐上移时候出现的UC头条和TabLayout。如下图(视频用UC转gif就变得好糊,好在还能看出动画):
在我看来,实现方法有两个:1.通过监听RecyclerView的触摸,然后判断是否让他滑动,以及他在位置改变的时候,TabLayout跟着变动;2.那就是今天的主题了,自定义behavior。第一种方法以前写的时候尝试过,比较难的在于,滑动过程中RecyclerView高度和位置的变化,它并不是简单的上移,单纯上移的话,底下会缺一块;再来就是触摸事件的分发和滑动的拦截了,以前写的时候失败了,bug很严重,对于事件分发的确不是很扎实,还得多学学。采用第二种,其实也会碰到这些难点,但是今天在于简单的实践,最简单地使用CoordinatorLayout。相信大家都有所了解,在写了AppBarLayout,滑动布局添加
app:layout_behavior="@string/appbar_scrolling_view_behavior"
滑动布局高度和滑动处理,就解决了,谷歌已经帮我们实现了。我们只需要把UC头条和tablayout依赖RecyclerView,根据RecyclerView的坐标做出改变就行了。下面给出代码,先是自定义Behavior,我使用了NestedScrollView代替RecyclerView,插数据比较好插,要依赖RecyclerView的自己替代一下:
public class MyBehavior extends CoordinatorLayout.Behavior {
private float allDistance = 0; // Head从和RecyclerView同样位置,移到顶部的距离
private float dependencyDistance = 0; //RecyclerView需要移动的距离
public MyBehavior() {
}
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (allDistance == 0) {
allDistance = dependency.getY();//获取child要移动的总距离,是dependency到顶部的距离
Log.d("allDistance", String.valueOf(allDistance));
dependencyDistance = allDistance - child.getHeight();//获取滑动控件所要移动的总距离,移动到childView下方
Log.d("recyclerDistance", String.valueOf(dependencyDistance));
child.setY(allDistance);//初始化child的位置,和dependency位置一样,不然child会在顶部
} else {
float distance = (allDistance - dependency.getY()) / dependencyDistance * allDistance;//child所需要移动的距离!
child.setY(allDistance - distance <= 0 ? 0 : allDistance - distance);//通过距离,计算出child的坐标,最后为0
}
return true;
}
}
这个behavior比较简单,根据dependency的Y坐标,计算出child的Y坐标。接下来是布局文件,比较重要:
这边有几个点,需要说一下,一个是AppBarLayout设置了一个background为全透明,原因是因为,在折叠完成后,AppBarLayout本来应该在LinearLayout下方,它会跑上来,变成顶层,不设置透明的话,自带的颜色会遮住LinearLayout。LinearLayout中的点击事件应该是不会和CollapsingToolBarLayout冲突的,我没有尝试,需要的小伙伴可以试一下。
然后就是在CollapsingToolbarLayout中设置,设置layout_scrollFlags为scroll|exitUntilCollapsed,snap可选可不选,作用是回弹效果,、android:minHeight="150dp"来控制滑动View能滑动到的距离,这个高度应该和TabLayout+Uc导航栏和搜索框所在的LinearLayout高度一致,这样的话,滑动的View就刚好能在LinearLayout下方。
设置CollapsingToolBarLayout的子控件的折叠:
app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="2.7"
重点在于layout_collapseParallaxMultiplier,网上叫做视觉差系数,就是折叠快慢的控制,之所以设置成这么大,是由于,不够大的情况下,CollapsingToolBarLayout没有收缩完全,里面控件会到上层显示,把LinearLayout遮住,设置大一些的话,会完全折叠,这就是我觉得点击事件不会冲突的原因,因为完全消失了,并不在LinearLayout所在的区域,自然就不会有点击冲突。
关键点:折叠布局CollapsingToolbarLayout的最小高度,要和滑动出现的LinearLayout高度一致,这是为了控制滑动控件,比如RecyclerView,NestedScrollView的滑动距离;设置layout_collapseParallaxMultiplier系数大一点,可以自己设置小一点看看效果,上面的布局文件的话,导航栏三个字就会出现在LinearLayout所在的区域,下面上效果图:
额,大致是这样,后面尝试不使用AppBarLayout,再试试吧