官方文档的第一句话就非常醒目:CoordinatorLayout is a super-powered FrameLayout,非常直白,CoordinatorLayout继承于ViewGroup,它就是一个超级强大Framelayout。说白了就是可以通过Behavior协调子View 。
<android.support.design.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/coordinatorLayout"
xmlns:app="http://schemas.android.com/apk/res-auto">
<View
android:id="@+id/view_girl"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginLeft="200dp"
android:background="@mipmap/make_music_voice_changer_female" />
<View
android:id="@+id/view_uncle"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@mipmap/make_music_voice_changer_uncle"
app:layout_behavior="com.example.md.mdview.RunBehavior"/>
</android.support.design.widget.CoordinatorLayout>
布局中有两个子view,操作viewgirl,viewuncle也会相应跟着走,这就要写一个联动关系,用自定义Behavior实现:
public class RunBehavior extends CoordinatorLayout.Behavior<View>{
public RunBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
int top = dependency.getTop();
int left = dependency.getLeft();
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
params.topMargin = top - 400;
params.leftMargin = left;
child.setLayoutParams(params);
return true;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return true;
}
}
CoordinatorLayout的使用核心是Behavior,Behavior就是执行你定制的动作。
在讲Behavior之前必须先理解两个概念:Child和Dependency,什么意思呢?Child当然是子View的意思了,是谁的子View呢,当然是CoordinatorLayout的子View;其实Child是指要执行动作的CoordinatorLayout的子View。而Dependency是指Child依赖的View。简而言之,就是如果Dependency这个View发生了变化,那么Child这个View就要相应发生变化。发生变化是具体发生什么变化呢?这里就要引入Behavior,Child发生变化的具体执行的代码都是放在Behavior这个类里面。
Behavior里面主要有两个方法:
-
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency)
判断child的布局是否依赖dependency,根据条件过滤判断返回值,返回true联动。如果返回flase不联动,即behavior不生效
-
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
当dependency发生改变时(位置、高度等),执行这个函数,返回true表示child的位置或者宽要发生变化,否则返回false
最后,别忘了在XML中通过layout_behavior来指定具体的行为:
app:layout_behavior="com.example.md.mdview.RunBehavior"
1、父布局肯定是CoordinatorLayout
2、一定会设置app:layout_scrollFlags
和app:layout_behavior
两个属性。
有同学可能会想一定要是 NestedScrollView 吗?
当然不是,在 CoordinatorLayout 中嵌套滑动的本质是一个 NestedScrollingChild 对象。
NestedScrollingChild 是一个接口,目前它的实现类有 4 个,分别是SwipeRefreshLayout、RecyclerView、NestedScrollView、NestedScrollView。
**滑动的View:**滑动的view官方建议使用NestedScrollView或者RecyclerView。ListView在5.0以下就没有效果了。 **高阶的使用:**高阶使用当然是自定义behavior了。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:onClick="onClick"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"/>
</RelativeLayout>
可以看到FloatingActionButton会被SmackBar遮挡,为了解决遮挡的问题,就需要使用到CoordinatorLayout。 CoordinatorLayout可以监听其所有子控件的各种事件,然后自动帮助我们做出最为合理的响应。如果我们能让CoordinatorLayout监听到Snackbar的弹出事件,那么它会自动将内部的FloatingActionButton向上偏移,从而确保不会被Snackbar遮挡到。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/contentView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/anchorView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/anchorView"
app:layout_anchorGravity="bottom|right"
android:onClick="onClick"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"/>
</android.support.design.widget.CoordinatorLayout>
这样之后,悬浮按钮会在弹出Snakebar的时候自动向上偏移了Snackbar的同等高度,从而确保不会被遮挡住,当Snackbar消失的时候,悬浮按钮会自动向下偏移回到原来位置。 另外悬浮按钮的向上和向下偏移也是伴随着动画效果的,且和Snackbar完全同步,整体效果看上去特别赏心悦目。
或者我们也可以不把FloatingActionButton放到布局中,只是make()方法时传入的view是在布局中即可,我们回过头来再思考一下,刚才说的是CoordinatorLayout可以监听其所有子控件的各种事件,但是Snackbar好像并不是CoordinatorLayout的子控件吧,为什么它却可以被监听到呢?
其实道理很简单,我们在Snackbar的make()方法中传入的参数就是用来指定Snackbar是基于哪个View来触发的,如果我们传入的是FloatingActionButton本身,而FloatingActionButton是CoordinatorLayout中的子控件,因此这个事件就理所应当能被监听到了。