CoordinatorLayout。
官方解释是:它是一个超级的FrameLayout,CoordinatorLayout的两个主要的用例:
作为一个顶级应用布局容器;
作为一个能够与一个或多个子视图交互的特定容器。
CoordinatorLayout.Behavior是个内部类,通过指定的Behavior可以完成内部的子控件的相互交互。Behavior是CoordinatorLayout用来和各个子View通信用的代理类。
CoordinatorLayout.Behavior类介绍
CoordinatorLayout.Behavior
官网对于 CoordinatorLayout.Behavior 的介绍已经将它的作用说明得很清楚了,就是用来协调 CoordinatorLayout 的Child Views之间的交互行为
1.使用自定义的Behavior
第一种方式:我们自定义的可以通过在布局中使用app:layout_behavior的来指定,
例如,我们在NestedScrollView设定的Android提供的Behavior,
app:layout_behavior="@string/appbar_scrolling_view_behavior">,
其中的string是Behavior的绝对路径。
第二种方式:如果你要用到自定义View/ViewGroup中,可以通过@CoordinatorLayout.DefaultBehavior()注解指定Behavior;这样我们的View就会默认使用这个Behavior。比如在AppBarLayout的使用方式:
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
//省略具体实现代码
}
第三种方式:通过代码设定Behavior,前提是你的指定的View必须是CoordinatorLayout的子View,如下:
MyBehavior myBehavior = new MyBehavior();
//我们的View必须是CoordinatorLayout的子View,否则我们获取不到CoordinatorLayout.LayoutParams
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) 我们的View.getLayoutParams();
params.setBehavior(myBehavior);
2.系统源码FloatingActionButton.Behavior
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton
这是design中的FloatingActionButton控件,他有一个自己的Behavior,使用上面说的方式二设置behavior,它有两个特点:
1.被固定在AppBarLayout中的时候伴随他的隐藏消失。
//FloatingActionButton和AppBarLayout建立固定关系
app:layout_anchor="@+id/a_design_appbarlayout"

2.依赖于SnakerBar的时候,当SnakerBar弹出消失的时候它要改变自己的位置。

源码如下:
public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
private ValueAnimatorCompat mFabTranslationYAnimator;
private float mFabTranslationY;
private Rect mTmpRect;
@Override
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
// 当API>11时候它依赖所有的Snackbar
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
//当Snakerbar改变的时候更新位置
updateFabTranslationForSnackbar(parent, child, dependency);
} else if (dependency instanceof AppBarLayout) {
//当和AppBarLayout建立固定关系的时候
updateFabVisibility(parent, (AppBarLayout) dependency, child);
}
return false;
}
//随AppBarLayout更新FloatingActionButton 的状态
private boolean updateFabVisibility(CoordinatorLayout parent,
AppBarLayout appBarLayout, FloatingActionButton child) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (lp.getAnchorId() != appBarLayout.getId()) {
// The anchor ID doesn't match the dependency, so we won't automatically
// show/hide the FAB
return false;
}
if (child.getUserSetVisibility() != VISIBLE) {
// The view isn't set to be visible so skip changing it's visibility
return false;
}
if (mTmpRect == null) {
mTmpRect = new Rect();
}
// First, let's get the visible rect of the dependency
final Rect rect = mTmpRect;
ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect);
if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
// If the anchor's bottom is below the seam, we'll animate our FAB out
child.hide(null, false);
} else {
// Else, we'll animate our FAB back in
child.show(null, false);
}
return true;
}
//随Snakerbar更新状态
private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
final FloatingActionButton fab, View snackbar) {
final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
if (mFabTranslationY == targetTransY) {
// We're already at (or currently animating to) the target value, return...
return;
}
final float currentTransY = ViewCompat.getTranslationY(fab);
// Make sure that any current animation is cancelled
if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
mFabTranslationYAnimator.cancel();
}
if (fab.isShown()
&& Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
// If the FAB will be travelling by more than 2/3 of it's height, let's animate
// it instead
if (mFabTranslationYAnimator == null) {
mFabTranslationYAnimator = ViewUtils.createAnimator();
mFabTranslationYAnimator.setInterpolator(
AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
mFabTranslationYAnimator.setUpdateListener(
new ValueAnimatorCompat.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimatorCompat animator) {
ViewCompat.setTranslationY(fab,
animator.getAnimatedFloatValue());
}
});
}
mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
mFabTranslationYAnimator.start();
} else {
// Now update the translation Y
ViewCompat.setTranslationY(fab, targetTransY);
}
mFabTranslationY = targetTransY;
}
private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
FloatingActionButton fab) {
float minOffset = 0;
final List<View> dependencies = parent.getDependencies(fab);
for (int i = 0, z = dependencies.size(); i < z; i++) {
final View view = dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
minOffset = Math.min(minOffset,
ViewCompat.getTranslationY(view) - view.getHeight());
}
}
return minOffset;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
int layoutDirection) {
// First, lets make sure that the visibility of the FAB is consistent
final List<View> dependencies = parent.getDependencies(child);
for (int i = 0, count = dependencies.size(); i < count; i++) {
final View dependency = dependencies.get(i);
if (dependency instanceof AppBarLayout
&& updateFabVisibility(parent, (AppBarLayout) dependency, child)) {
break;
}
}
// Now let the CoordinatorLayout lay out the FAB
parent.onLayoutChild(child, layoutDirection);
// Now offset it if needed
offsetIfNeeded(parent, child);
return true;
}
private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) {
final Rect padding = fab.mShadowPadding;
if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) fab.getLayoutParams();
int offsetTB = 0, offsetLR = 0;
if (fab.getRight() >= parent.getWidth() - lp.rightMargin) {
// If we're on the left edge, shift it the right
offsetLR = padding.right;
} else if (fab.getLeft() <= lp.leftMargin) {
// If we're on the left edge, shift it the left
offsetLR = -padding.left;
}
if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) {
// If we're on the bottom edge, shift it down
offsetTB = padding.bottom;
} else if (fab.getTop() <= lp.topMargin) {
// If we're on the top edge, shift it up
offsetTB = -padding.top;
}
fab.offsetTopAndBottom(offsetTB);
fab.offsetLeftAndRight(offsetLR);
}
}
}
3.自定义Behavior
1.依赖机制
这种机制描述的是两个Child Views之间的绑定依赖关系,主要依赖:
layoutDependsOn()
onLayoutDependencyChanged()
这两个方法,第一个很显然是告诉CoordinatorLayout,一个View是否依赖于另一个View。
第二个是CoordinatorLayout发现存在依赖的时候,把被依赖方回调给依赖方,因为这时候,layout已经完成,我们可以获取被依赖方的所有布局信息,根据布局信息,使用offset来决定依赖方的一些位置;同时在这个时候,你也可以调用requestLayout进行重新布局。
上面说到的FloatingActionButton.Behavior就属于这一种。
/**
* 告诉CoordinatorLayout我要依赖那个view,这里我明确要依赖first
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == R.id.first;
}
/**
*当我们监听的view有任何变化都会回调这个方法。我们在这里面改变监听者的位置
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
child.setTranslationY(dependency.getTranslationY()+dependency.getHeight());
return true;
}
例子:
public class MyBottomBarBehavior extends CoordinatorLayout.Behavior<View> {
public MyBottomBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//这个方法是说明这个子控件是依赖AppBarLayout的
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.abs(dependency.getTop());//获取更随布局的顶部位置
child.setTranslationY(translationY);
return true;
}
}
2.监听滑动

CoordinatorLayout是实现了NestedScrollingParent接口的,也就是说,要用到这个特性的话,默认不实现NestedScrollingChild接口 且不调用dispatchNestedScroll相关接口的View就没有这些效果了。
主要是这3个方法,将依赖对象的滑动事件都将通知进来:
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;//这里返回true,才会接受到后续滑动事件。
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//进行滑动事件处理
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
//当进行快速滑动
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
项目下载地址:链接:http://pan.baidu.com/s/1pLt2YvD 密码:r095
完整的例子:https://github.com/zhaochenpu/CoordinatorLayoutDemo
参考:
https://developer.android.com/reference/android/support/design/widget/CoordinatorLayout.html
http://www.tuicool.com/articles/IZnIBbf
http://www.tuicool.com/articles/Uj6Nvqj
https://github.com/zhaochenpu/CoordinatorLayoutDemo
网友评论