CoordinatorLayout,是Material风格的重要组件, 作为布局的顶层控件,协调(Coordinate)其他组件, 实现联动。在我们新建Activity的时候,可以通过ScrollingActivity模板,创建一个标准的CoordinatorLayout布局的Activity,相信大家一定见识过,网上介绍CoordinatorLayout使用方法的文章也有很多,这里我就不再说了。今天想介绍的是CoordinatorLayout的高级用法,自定义Behavior。
还记得那一串字符串吗?

app:layout_behavior="@string/appbar_scrolling_view_behavior"

其实它并不是一个字符串资源,而是代表了一个类,就是一个Behavior。

认识Behavior

Behavior是CoordinatorLayout的一个抽象内部类

public abstract static class Behavior<V extends View> {
      public Behavior() {
      }

      public Behavior(Context context, AttributeSet attrs) {
      }

}

有一个泛型是指定的我们应用这个Behavior的View的类型,例如上面的appbar_scrolling_view_behavior对应的字符串其实是Android.support.design.widget.AppBarLayout$ScrollingViewBehavior。
在Behavior中有两个概念,dependency和child,很好理解,dependency在这里指的是被依赖的View,child是指需要依赖的View,也就是说,child依赖于dependency,当dependency做出变化时,child可以做出相应的反应。

如何自定义

public class MyBehavior extends CoordinatorLayout.Behavior<View>{
        public MyBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    }

一定要重写这个构造函数。因为CoordinatorLayout源码中parseBehavior()函数中直接反射调用这个构造函数。可以看到这里指定了一个泛型,这个泛型其实指定的就是child的泛型,因此,如果没有特殊需求,直接指定view为View就行了。

在任意View中添加:
app:layout_behavior=“完整类名”然后CoordinatorLayout就会反射生成你的Behavior。

另外一种方法如果你的自定义View默认使用一个Behavior。
在你的自定义View类上添加@DefaultBehavior(你的Behavior.class)这句注解。
你的View就默认使用这个Behavior。就像AppBarLayout一样。

@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}

生成Behavior后第一件事就是确定依赖关系。重写Behavior的这个方法来确定你依赖哪个View。

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency.getId() == R.id.first;

我们可以按照两种目的来实现自己的Behavior,当然也可以两种都实现

1.child监听dependency的状态变化,例如大小、位置、显示状态等
2.child监听实现了NestedScrollingChild的接口的dependency的滑动状态
第一种情况需要重写layoutDependsOn和onDependentViewChanged方法
第二种情况需要重写onStartNestedScroll和onNestedPreScroll系列方法

先来说第一种情况:

android 自定义属性编译 android 自定义behavior_android

很简单,顶部、底部分别有一个导航栏,右下角一个FloatingActionButton,底部导航栏和FloatingActionButton随着顶部导航栏的变化而变化。

XML文件

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="0dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="我是TopBar"
            app:layout_scrollFlags="scroll|enterAlways" />
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        app:behavior_overlapTop="30dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.v7.widget.CardView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                app:cardElevation="8dp"
                app:contentPadding="16dp">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:lineSpacingExtra="8dp"
                    android:text="@string/content"
                    android:textSize="18sp" />
            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                app:cardElevation="8dp"
                app:contentPadding="16dp">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:lineSpacingExtra="8dp"
                    android:text="@string/content"
                    android:textSize="18sp" />
            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                app:cardElevation="8dp"
                app:contentPadding="16dp">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:lineSpacingExtra="8dp"
                    android:text="@string/content"
                    android:textSize="18sp" />
            </android.support.v7.widget.CardView>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_gravity="bottom"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="我是BottomBar"
        app:layout_behavior=".BottomBehavior" />

    <android.support.design.widget.FloatingActionButton
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_gravity="bottom|right"
        android:layout_marginBottom="70dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/ic_launcher_round"
        app:backgroundTint="#ffffff"
        app:elevation="5dp"
        app:layout_behavior=".FloatingBehavior" />
</android.support.design.widget.CoordinatorLayout>

分别给FloatingActionButton和底部导航栏设置了
app:layout_behavior=”.FloatingBehavior”和app:layout_behavior=”.BottomBehavior”
AppBarLayout的滑动隐藏显示就不说了

下面来看自定义Behavior的实现

BottomHehavior

public class BottomBehavior extends CoordinatorLayout.Behavior<View> {
    public BottomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        //设置依赖View为AppBarLayout(如果dependency的类型在布局文件中有多个,就不能这样写,而是通过id来判断)
        return dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float scale = Math.abs(dependency.getY()) / dependency.getHeight();
        child.setTranslationY(child.getHeight() * scale );
        return true;
    }
}

实现也很简单,在onDependentViewChanged中根据dependency.getY的变化改变child的TranslationY。FloatingBehavior和这个一样,通过scale 这个值来设置child的scaleX和scaleY即可。

滑动的情况,就留到下次再讲,嗯,就这样。
ps:自定义Behavior是可以使用自定义属性的,就和自定义控件中的用法一样。