- 协调其子view们之间动作的一个父view,作为顶层应用的装饰;
- 作为一个能响应特定的一个或多个子视图交互的容器。
- CoordinatorLayout须作为顶层父View
- 子View想要与CoordinatorLayout实现"联动性"效果的首要条件是这个View必须实现了NestedScrollingChild接口 RecyclerView,NestedScrollView等新的控件都实现这个接口,ListView这些旧的控件就无法使用了.
- 只有CoordinatorLayout的直接子View才会有效果,子View的子View无效
Behavior用于当前控件的父控件CoordinatorLayout中的其他子控件的关系,Behavior作为一个中间者,目的是协调子控件之间的触摸事件
- 使用Behavior的控件必须是直接从属于CoordinatorLayout,否者无效
- 自定义Behavior的时候必须覆盖下面的构造方法,因为通过反射实例化的时候用的就是该构造方法.
我们今天并不是学习如何使用CoordinatorLayout 这个组件,而是仿照这个组件的事件转发原理完成一个自定义的CoordinatorLayout ,完成和原版CoordinatorLayout 一样的效果,旨在学习其原理,真正在项目中使用还是用原版的CoordinatorLayout 即可。
下面来看下完成后的整体效果,项目完整地址:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn16
根据手指滑动距离,完成显示隐藏图片以及透明度的设置
获取自定义属性:我们自定义的BehaviorCoordinatorLayout,首先仿照CoordinatorLayout封装LayoutParams以及使用反射获取自定义属性的代码:
public static class LayoutParams extends RelativeLayout.LayoutParams {
private static final String TAG = "touch";
private Behavior behavior;
public Behavior getBehavior() {
return behavior;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.BeaviorCoordinatorLayout);
behavior = parseBehavior(c, attrs, a.getString(R.styleable.BeaviorCoordinatorLayout_layout_behavior));
Log.i(TAG, "LayoutParams: 名字 " + a.getString(R.styleable.BeaviorCoordinatorLayout_layout_behavior));
a.recycle();
}
private Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
try {
final Class clazz = Class.forName(name, true, context.getClassLoader());
Constructor c = clazz.getConstructor(new Class[]{Context.class, AttributeSet.class});
c.setAccessible(true);
return (Behavior) c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " + name, e);
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
完成事件转发:
获取到Behavior之后,就需要完成事件的转发,例如我们需要监听滑动事件onNestedScroll,就需要我们将该事件进行转发到Behavior中去,
/**
* 最重要
* // 参数target:同上
* // 参数dxConsumed:表示target已经消费的x方向的距离
* // 参数dyConsumed:表示target已经消费的y方向的距离
* // 参数dxUnconsumed:表示x方向剩下的滑动距离
* // 参数dyUnconsumed:表示y方向剩下的滑动距离
*/
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams param = (LayoutParams) child.getLayoutParams();
if (param.getBehavior() != null) {
param.getBehavior().onNestedScroll(target, child, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
}
}
这里需要注意的是需要实现onStartNestedScroll这个方法,否则无法将滑动事件转发到Behavior中:
/**
* ----------------滚动事件------------------move------------
* move 肯定是拿不到
* 一定返回 true
* 实现了 NestedScrolling机制 的 滚动控件
*/
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
最后就是在需要监听滑动事件的地方完成具体业务逻辑即可,例如我们的ImageBehavior中需要根据上下的滑动距离进行图片露出高度的设置,在ToolBarBehavior中则改变透明度即可:
ToolBarBehavior完成透明度变化的设置:public class ToolBarBehavior extends Behavior {
private int maxHeight = 400;
public ToolBarBehavior(Context context, AttributeSet set) {
super(context, set);
}
/**
* 进行透明度吧变换
*/
@Override
public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(scrollView, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (scrollView.getScrollY() 0) {
ViewGroup.LayoutParams parmas = target.getLayoutParams();
Log.i(TAG, "onNestedScroll: parmas.height " + parmas.height + " originHeight " + originHeight);
parmas.height = parmas.height - Math.abs(dyConsumed);
if (parmas.height < originHeight) {
parmas.height = originHeight;
}
target.setLayoutParams(parmas);
} else if (scrollView.getScrollY() == 0) {
ViewGroup.LayoutParams params = target.getLayoutParams();
params.height = params.height + Math.abs(dyUnconsumed);
if (params.height >= maxHeight) {
params.height = maxHeight;
}
target.setLayoutParams(params);
}
}
}