在Design Support Library中,Google为我们提供了一系列实现Material Design的控件:TextInputLayout、NavigationView、FloatingActionButton等。

CoordinatorLayout,为我们处理触摸事件提供了一种新的方式。在CoordinatorLayout的使用中,最为核心的就是CoordinatorLayout.Behavior这个内部类了

从CoordinatorLayout开始

CoordinatorLayout is a super-powered FrameLayout. CoordinatorLayout is intended for two primary use cases:

  1. As a top-level application decor or chrome layout
  2. As a container for a specific interaction with one or more child views

从开发者文档的描述中我们可以知道,CoordinatorLayout是一个加强版的FrameLayout,他有两个主要作用:

  1. 作为根布局
  2. 用于处理一个或多个子控件之间特殊的交互的容器

何为多个子控件之间的交互呢?在之前的博客(ANDROID 触摸事件分发源码)中有提及,在ViewGroup处理触摸事件时,只会在ACTION_DOWN的时候去判断哪一个子控件可以接收处理事件;当某一个子控件可以处理ACTION_DOWN事件时,后续的一系列手势会直接传递到该控件,其他子控件是无法直接响应触摸事件的。

如果我们需要某个子控件监听另一个子控件、或者监听父控件的滚动,并做出响应?通常我们会定义一个接口,通过接口的回调在另一个控件中修改属性。

而现在,可以直接通过CoordinatorLayout对这些控件进行包裹,来处理这些子控件之间的交互

CoordinatorLayout.Behavior

那么,怎样才能在CoordinatorLayout中进行子控件之间的交互呢?这就需要我们来自定义不同的CoordinatorLayout.Behavior了

Behavior是CoordinatorLayout的内部类,在这个类的内部提供了一系列的回调方法:

  1. layoutDependsOn():将两个控件绑定起来,child监听dependency
  2. onDependentViewChanged():在dependency状态改变时回调
  3. onStartNestedScroll ():根据返回值来决定是否要处理这次neast scroll
  4. onNestedPreScroll(): 在子控件响应滑动之前回调
  5. onNestedScroll():子控件响应滑动时回调
  6. onNestedPreFling()|onNestedFling():惯性滑动

主要需要重写的就是这些方法,通常

  1. 如果需要让某个控件监听另一个控件的状态,需要重写layoutDependsOn()onDependentViewChanged()方法
  2. 如果需要让某个控件监听父控件的滑动,需要重写另外四个方法中的部分或全部

自定义一个Beahavior

现在我们有这么一个需求,在CoordinatorLayout中有一个RecyclerView和一个FAB,需要在RecyclerView下滑时隐藏FAB、上滑时显示FAB。现在大多数使用Material Design的App都会有这么一个需求

public class ScrollBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {

    // 由于需要在布局文件中使用,所以必须重写这个构造函数
    public ScrollBehavior(Context context, AttributeSet attrs) {
        super();
    }

    @Override
    public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
                                       final View directTargetChild, final View target, final int nestedScrollAxes) {
        // 对垂直方向的滑动进行处理
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                        target, nestedScrollAxes);
    }

    @Override
    public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, final
    View target, final int dxConsumed, final int dyConsumed, final int dxUnconsumed, final int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed);
        if (dyConsumed > 0 &&
                child.getVisibility() == View.VISIBLE) {
            // 用户向下滑,并且FAB是VISIBLE时 -> hide the FAB
            child.hide();
            child.setVisbility(View.GONE);
        } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
            // 用户向上滑,并且FAB当前不是VISIBLE时 -> show the FAB
            child.setVisbility(View.VISIBLE);
            child.show();
        }
    }
}

自定义Behavior之后,在布局文件中给FloatingActionButton添加上属性,就可以实现想要的UI了

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/main_content"
    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.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        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_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@android:drawable/ic_dialog_email"
        app:layout_behavior=".ScrollBehavior"/>
</android.support.design.widget.CoordinatorLayout>

要注意的是,在CoordinatorLayout中,使用ListView或者GridView作为传递滑动的控件是不能响应CoordinatorLayout.Behavior的。 似乎是因为ListView和GridView中没有添加相关的代码。使用RecyclerView就可以了