通过上两篇,我们了解了CoordinatorLayout+CollapsingToolbarLayout如何实现我们想要的折叠效果,那么我们继续来学习一下Behavior!CoordinatorLayout中最经典的设计应该就是Behavior,在前面,我们已经提到了
app:layout_behavior="@string/appbar_scrolling_view_behavior"
,其实app:layout_behavior="@string/appbar_scrolling_view_behavior"
对应的是AppBarLayout.ScrollingViewBehavior
。我们可以自定义Behavior来实现自己的组件和滑动交互等功能。自定义的Behavior可以分为两种方法:第一种是定义View监听CoordinatorLayout里的滑动状态;第二种是定义的View监听另一个View的状态变化,例如View的大小,位置和显示状态等。
1、某个view监听CoordinatorLayout内NestedScrollingChild的接口实现类的滑动状态
重写onStartNestedScroll和onNestedPreScroll方法。注意:是监听实现了NestedScrollingChild的接口实现类的滑动状态,这就可以解释为什么不能用ScrollView而用NestScrollView来滑动了。
2、某个view监听另一个view的状态变化,例如大小、位置、显示状态等
需要重写layoutDependsOn和onDependentViewChanged方法
一:Behavior的方法:
1.layoutDependsOn
确定提供的子视图是否具有另一个特定的兄弟视图作为布局依赖关系。即用来确定依赖关系,如果某个控件需要依赖控件,则重写该方法,如AppBarLayout
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
2.onDependentViewChanged
依赖视图的大小、位置发生变化时调用此方法,重写此方法可以处理child的响应。如常用的AppBarLayout,当其发生变化时,childView会根据重写的方法作出响应。
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
offsetChildAsNeeded(parent, child, dependency);
return false;
}
3.onStartNestedScroll
当CoordinatorLayout的子View开始嵌套滑动时(此处的滑动View必须实现NestedScrollingChild接口),触发此方法。添加Behavior的控件需要为CoordinatorLayout的直接子View,否则不会继续流程。
//判断是否垂直滑动
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
4.onNestedPreScroll
此方法中consumed,指的是父布局要消费的滚动距离,consumed[0]为水平方向消耗的距离,consumed[1]为垂直方向消耗的距离,可控制此参数作出相应的调整。
如垂直滑动时,若设置consumed[1]=dy,则代表父布局全部消耗了滑动的距离,类似AppBarLayout这种效果,当其由展开到折叠过渡时,通过consumed控制其中的嵌套滑动。
/**
* 触发滑动嵌套滚动之前调用的方法
*
* @param coordinatorLayout coordinatorLayout父布局
* @param child 使用Behavior的子View
* @param target 触发滑动嵌套的View(实现NestedScrollingChild接口)
* @param dx 滑动的X轴距离
* @param dy 滑动的Y轴距离
* @param consumed 父布局消费的滑动距离,consumed[0]和consumed[1]代表X和Y方向父布局消费的距离,默认为0
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target,
int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
5.onNestedScroll
此方法中dyConsumed代表TargetView消费的距离,如RecyclerView滑动的距离,可通过控制NestScrollingChild的滑动来指定一些动画,
本篇博客实现的效果主要就是重写此方法,若根据onNestedPreScroll中dy来判断,则当RecyclerView条目很少时,也会触发逻辑代码,故选择了重写此方法。
/**
* 滑动嵌套滚动时触发的方法
*
* @param coordinatorLayout coordinatorLayout父布局
* @param child 使用Behavior的子View
* @param target 触发滑动嵌套的View
* @param dxConsumed TargetView消费的X轴距离
* @param dyConsumed TargetView消费的Y轴距离
* @param dxUnconsumed 未被TargetView消费的X轴距离
* @param dyUnconsumed 未被TargetView消费的Y轴距离(如RecyclerView已经到达顶部或底部,
* 而用户继续滑动,此时dyUnconsumed的值不为0,可处理一些越界事件)
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target,
dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
二:Behavior自定义的实现:
xml:
Behavior自定义类:第一种是定义View监听CoordinatorLayout里的滑动状态
public class MessageBehavior extends CoordinatorLayout.Behavior {
private int directionChange;
private boolean isInit=true;
public MessageBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) !=0;
//return true; //判断是纵向或横向
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
if (dy > 0 && directionChange < 0 || dy < 0 && directionChange > 0) {
child.animate().cancel();
directionChange = 0;
}
directionChange += dy;
if (directionChange > child.getHeight() && child.getVisibility() == View.VISIBLE) {
hide(child);
} else if (directionChange < 0 && child.getVisibility() == View.GONE) {
show(child);
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
private void hide(final View view){
ViewPropertyAnimator animator=view.animate().translationY(view.getHeight()).setInterpolator(new FastOutSlowInInterpolator()).setDuration(200);
animator.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.GONE);
}
});
animator.start();
}
private void show(final View view){
ViewPropertyAnimator animator=view.animate().translationY(0).setInterpolator(new FastOutSlowInInterpolator()).setDuration(200);
animator.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.VISIBLE);
}
});
animator.start();
}
}
Behavior自定义类:第二种是定义的View监听另一个View的状态变化
public class MessageBehavior2 extends CoordinatorLayout.Behavior {
public MessageBehavior2(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;//依赖的是AppBarLayout
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//取绝对值
float translationY=Math.abs(dependency.getY());//获得被监听控件的移动值
child.setTranslationY(translationY);//将被监控控件的移动值 绝对化(反方向) 赋值给需要移动的控件
return true;
}
}
Behavior自定义类:第二种是定义的View监听另一个View的状态变化 如果我们不知道要依赖的控件,也可以由外部传入,在构造方法中获取资源ID来进行判断,这样具有更高的灵活性
- 增加attrs.xml:
- 主布局XML中需要增加:app:anchor_id="@id/appbar"
public class MessageBehavior2 extends CoordinatorLayout.Behavior {
private int aId;
public MessageBehavior2(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.getResources().
obtainAttributes(attrs, R.styleable.MyCustomStyle);
aId= typedArray.getResourceId(R.styleable.MyCustomStyle_anchor_id, -1);
typedArray.recycle();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == aId;//依赖的是AppBarLayout
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//取绝对值
float translationY=Math.abs(dependency.getY());//获得被监听控件的移动值
child.setTranslationY(translationY);//将被监控控件的移动值 绝对化(反方向) 赋值给需要移动的控件
return true;
}
}
最终的运行效果如图: