一、CoordinatorLayout
CoordinatorLayout的主要功能是协调内部各个子控件直接的状态关系,也就是说,可以协调多个View进行互动,比如:移动,动画等。它是通过Behavior。代码连接已经放在了最下面,有需要可以下载
二、Behavior
是作用于CoordinatorLayout的子View的交互行为插件。Google给我们提供了一些Behavior,我们也可以自己定义Behavior,代码在最下面
1. BottomSheetBehavior
它是一个从底部弹出一个布局,例如我们经常用的分享功能
1.1 用法
注:我们设置了一个Behavior,bottom_sheet_behavior,是系统提供好的一个behavior,如果一开始需要隐藏的话,可以设置app:behavior_peekHeight="0dp"
然后在代码中这样写
BottomSheetBehavior有5种状态
(1)STATE_EXPANDED展开状态,显示完整布局。
(2)STATE_COLLAPSED折叠状态,显示peekHeigth 的高度,如果peekHeight为0,则全部隐藏,与STATE_HIDDEN效果一样。
(3)STATE_DRAGGING拖拽时的状态
(4)STATE_HIDDEN隐藏时的状态
(5)STATE_SETTLING释放时的状态
2. BottomSheetDialog
它是一个Dialog,从底部弹出一个Dialog,比如淘宝商品详情页的立即购买,它是对BottomSheetBehavior的一个封装,是获取一个Behavior,设置一个监听状态的回调,设置了下滑可以隐藏
示例如下:
注意:系统的BottomSheetDialog是基于BottomSheetBehavior封装的,这里判断了当滑动隐藏了BottomSheetBehavior中的View后,内部设置了BottomSheetBehavior的状态为STATE_HIDDEN,所以我们再次调用dialog.show()的时候Dialog没法再打开状态为STATE_HIDE的Dialog,所以我们需要自己来实现,监听用户滑动关闭后,把BottomSheetBehavior的状态再设置为STATE_COLLAPSED
3.SwipeDissmissBehavior
滑动关闭或者滑动消失,Snackbar就是使用的这个,当滑动Snackbar的时候,Snackbar消失
代码也特别简单,在代码中直接new一个SwipeDissmissBehavior,设置属性,添加到CoordinatorLayout.LayoutParams,直接代码截图
4. 自定义Behavior
Google为我们提供了一些场景使用的Behavior,但是有时候,要实现多个View之间的交互,我们可以使用自定义Behavior
4.1 第一种是通过监听一个View的状态,如果位置,大小的变化,来改变其它View的行为,这种只需要重写两个方法就可以了,分别是layoutDependsOn和onDependentViewChanged,layoutDependsOn方法判断是指定依赖的View时,返回true,然后在onDependentViewChange里,被依赖的View做需要的行为动作
4.2 第二种就是重写onStartNestedScroll、onNestedPreScroll等
具体方法:
/****
* 表示是否给应用了Behavior 的View 指定一个依赖的布局,通常,当依赖的View 布局发生变化时
* 不管被被依赖View 的顺序怎样,被依赖的View也会重新布局
* @param parent
* @param child 绑定behavior 的View
* @param dependency 依赖的view
* @return 如果child 是依赖的指定的View 返回true,否则返回false
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 当被依赖的View 状态(如:位置、大小)发生变化时,这个方法被调用
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
/**
* 当coordinatorLayout 的子View试图开始嵌套滑动的时候被调用。当返回值为true的时候表明
* coordinatorLayout 充当nested scroll parent 处理这次滑动,需要注意的是只有当返回值为true
的时候,Behavior 才能收到后面的一些nested scroll 事件回调(如:onNestedPreScroll、onNestedScroll等)
这个方法有个重要的参数axes,表明处理的滑动的方向。
* @param coordinatorLayout 和Behavior 绑定的View的父CoordinatorLayout
* @param child 和Behavior 绑定的View
* @param directTargetChild
* @param target
* @param axes 嵌套滑动 应用的滑动方向(ViewCompat.SCROLL_AXIS_HORIZONTAL,@ViewCompat.SCROLL_AXIS_VERTICAL)
* @param type
* @return
*/
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,@NonNull FloatingActionButton child,@NonNull View directTargetChild,@NonNull View target,int axes,int type) {
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes);
}
/**
* 嵌套滚动发生之前被调用
* 在nested scroll child 消费掉自己的滚动距离之前,嵌套滚动每次被nested scroll child
* 更新都会调用onNestedPreScroll。注意有个重要的参数consumed,可以修改这个数组表示你消费
了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,
这样coordinatorLayout就能知道只处理剩下的10px的滚动。
* @param coordinatorLayout
* @param child
* @param target
* @param dx 用户水平方向的滚动距离
* @param dy 用户竖直方向的滚动距离
* @param consumed
* @param type
*/
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,@NonNull FloatingActionButton child,@NonNull View target,int dx,int dy,@NonNull int[] consumed,int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
/**
* 进行嵌套滚动时被调用
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed 已经消费的x方向的距离
* @param dyConsumed 已经消费的y方向的距离
* @param dxUnconsumed x 方向剩下的滚动距离
* @param dyUnconsumed y 方向剩下的滚动距离
* @param type
*/
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,@NonNull FloatingActionButton child,@NonNull View target,int dxConsumed,int dyConsumed,int dxUnconsumed,int dyUnconsumed,int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
}
/**
* 嵌套滚动结束时被调用,这是一个清除滚动状态等的好时机。
* @param coordinatorLayout
* @param child
* @param target
* @param type
*/
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,@NonNull FloatingActionButton child,@NonNull View target,int type) {
super.onStopNestedScroll(coordinatorLayout, child, target, type);
}
/**
* onStartNestedScroll返回true才会触发这个方法,接受滚动处理后回调,可以在这个
方法里做一些准备工作,如一些状态的重置等。
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param axes
* @param type
*/
@Override
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,@NonNull FloatingActionButton child,@NonNull View directTargetChild,@NonNull View target,int axes,int type) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes, type);
}
/**
* 用户松开手指并且会发生惯性动作之前调用,参数提供了速度信息,可以根据这些速度信息决定最终状态,
比如滚动Header,是让Header处于展开状态还是折叠状态。返回true 表示消费了fling.
* @param coordinatorLayout
* @param child
* @param target
* @param velocityX x 方向的速度
* @param velocityY y 方向的速度
* @return
*/
@Override
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,@NonNull FloatingActionButton child,@NonNull View target,float velocityX,float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
/**
* /可以重写这个方法对子View 进行重新布局
* @param parent
* @param child
* @param layoutDirection
* @return
*/
@Override
public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,int layoutDirection) {
return super.onLayoutChild(parent, child, layoutDirection);
}
案例:仿知乎首页滑动隐藏/显示
它是CoordinatorLayout的子View之间的交互,实现子View随着RecyclerView的滚动显示或者隐藏,只需要滑动一段距离,来显示隐藏,实现onNestedScroll(),在里面判断View需要移动的位置,然后在进行动画移动
//向上的时候是出来,向下是隐藏
if(dyConsumed>0){//往上滑动,是隐藏,需要加一个标志位
if(!isOut){//不是往下走,需要往下走
CoordinatorLayout.LayoutParams params=(CoordinatorLayout.LayoutParams)child.getLayoutParams();
child.animate().translationY(params.bottomMargin+child.getMeasuredHeight()).setDuration(300).start();
//处理底部位移动画
mBottomTabView.animate().translationY(mBottomTabView.getMeasuredHeight()).setDuration(300).start();
isOut=true;
}
}else {//往下滑动
if(isOut){
child.animate().translationY(0).setDuration(300).start();
//处理底部位移动画
mBottomTabView.animate().translationY(0).setDuration(300);
isOut=false;
}
}
注:1.自定义Behavior构造方法一定要重载!!!不然会报错
2.底部移动和隐藏需要先在onLayoutChild()里面获取底部控件id,然后再在onNestedScroll()写移动动画
Override
public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,int layoutDirection) {
mBottomTabView = parent.findViewById(R.id.bottom_tab_layout);
return super.onLayoutChild(parent, child, layoutDirection);
}
