CoordinatorLayout

0.302字数 3612阅读 4516

CoordinatorLayout与滚动的处理


CoordinatorLayout实现了多种Material Design中提到的滚动效果。目前这个框架提供了几种不用写动画代码就能工作的方法,这些效果包括:

  • 让浮动操作按钮上下滑动,为Snackbar留出空间。
  • 扩大或缩小Toolbar或者头部,让主内容区域有更多的空间。
  • 控制哪个view应该扩展还是收缩,以及其显示大小比例,包括视差滚动效果动画。

设置:
首先确保遵循了Design Support Library的使用说明。

浮动操作按钮与Snackbar

CoordinatorLayout可以用来配合浮动操作按钮的layout_anchor和layout_gravity属性创造出浮动效果,详情请参见浮动操作按钮按钮。

当Snackbar在显示的时候,往往出现在屏幕的底部。为了给Snackbar留出空间,浮动操作按钮需要向上移动。

只要使用CoordinatorLayout作为基本布局,将自动产生向上移动的动画。浮动操作按钮有一个 默认的 behavior来检测Snackbar的添加并让按钮在Snackbar之上呈现上移与Snackbar等高的动画。

<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/rvToDoList"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 </android.support.v7.widget.RecyclerView>

 <android.support.design.widget.FloatingActionButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|right"
    android:layout_margin="16dp"
    android:src="@mipmap/ic_launcher"
    app:layout_anchor="@id/rvToDoList"
    app:layout_anchorGravity="bottom|right|end"/>
 </android.support.design.widget.CoordinatorLayout>

Toolbar的扩展与收缩

首先需要确保你不是使用已经过时的ActionBar。务必遵循 使用ToolBar作为actionbar这篇文章的指南。同样,这里也需要CoordinatorLayout作为主布局容器。

<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/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

  <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

</android.support.design.widget.CoordinatorLayout>
响应滚动事件

接下来,我们必须使用一个容器布局:AppBarLayout来让Toolbar响应滚动事件。

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/detail_backdrop_height"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:fitsSystemWindows="true">

  <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

 </android.support.design.widget.AppBarLayout>

注意:根据官方的谷歌文档,AppBarLayout目前必须是第一个嵌套在CoordinatorLayout里面的子view。
然后,我们需要定义AppBarLayout与滚动视图之间的联系。在RecyclerView或者任意支持嵌套滚动的view比如NestedScrollView上添加app:layout_behavior。support library包含了一个特殊的字符串资源@string/appbar_scrolling_view_behavior,它和AppBarLayout.ScrollingViewBehavior相匹配,用来通知AppBarLayout 这个特殊的view何时发生了滚动事件,这个behavior需要设置在触发事件(滚动)的view之上。

<android.support.v7.widget.RecyclerView
    android:id="@+id/rvToDoList"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

当CoordinatorLayout发现RecyclerView中定义了这个属性,它会搜索自己所包含的其他view,看看是否有view与这个behavior相关联。AppBarLayout.ScrollingViewBehavior描述了RecyclerView与AppBarLayout之间的依赖关系。RecyclerView的任意滚动事件都将触发AppBarLayout或者AppBarLayout里面view的改变。

AppBarLayout里面定义的view只要设置了app:layout_scrollFlags属性,就可以在RecyclerView滚动事件发生的时候被触发:

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways"/>

 </android.support.design.widget.AppBarLayout>

app:layout_scrollFlags属性里面必须至少启用scroll这个flag,这样这个view才会滚动出屏幕,否则它将一直固定在顶部。可以使用的其他flag有:

  • enterAlways: 一旦向上滚动这个view就可见。
  • enterAlwaysCollapsed: 顾名思义,这个flag定义的是何时进入(已经消失之后何时再次显示)。假设你定义了一个最小高度(minHeight)同时enterAlways也定义了,那么view将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完。
  • exitUntilCollapsed: 同样顾名思义,这个flag时定义何时退出,当你定义了一个minHeight,这个view将在滚动到达这个最小高度的时候消失。

记住,要把带有scroll flag的view放在前面,这样收回的view才能正常退出,而固定的view继续留在底部。

制造折叠效果

如果想制造Toolbar的折叠效果,我们必须把Toolbar放在CollapsingToolbarLayout中:

<android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsing_toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        app:contentScrim="?attr/colorPrimary"
        app:expandedTitleMarginEnd="64dp"
        app:expandedTitleMarginStart="48dp"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">
        
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways"></android.support.v7.widget.Toolbar>
    </android.support.design.widget.CollapsingToolbarLayout>

通常,我们呢都是设置Toolbar的title,而现在,我们需要把title设置在CollapsingToolBarLayout上,而不是Toolbar。

CollapsingToolbarLayout collapsingToolbar =
          (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
  collapsingToolbar.setTitle("Title");
制造视差效果

CollapsingToolbarLayout还能让我们做出更高级的动画,比如在里面放一个ImageView,然后在它折叠的时候渐渐淡出。同时在用户滚动的时候title的高度也会随之改变。

为了制造出这种效果,我们添加了一个定义了app:layout_collapseMode = "parallax"属性的ImageView

  <android.support.design.widget.CollapsingToolbarLayout
      android:id="@+id/collapsing_toolbar"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:fitsSystemWindows="true"
      app:contentScrim="?attr/colorPrimary"
      app:expandedTitleMarginEnd="64dp"
      app:expandedTitleMarginStart="48dp"
      app:layout_scrollFlags="scroll|exitUntilCollapsed">

      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent"
          android:layout_height="?attr/actionBarSize"
          app:layout_scrollFlags="scroll|enterAlways"></android.support.v7.widget.Toolbar>
      <ImageView
          android:src="@drawable/cheese_1"
          app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:scaleType="centerCrop"
          app:layout_collapseMode="parallax"
          android:minHeight="100dp"/>

  </android.support.design.widget.CollapsingToolbarLayout>

CoordinatorLayout的工作原理是搜索定义了CoordinatorLayout Behavior的子view,不管是通过在XML中使用app:layout_behavior标签还是通过在代码中对view类使用@DefaultBehavior修饰符来添加注解。当滚动发生的时候,CoordinatorLayout会尝试触发那些声明了依赖的子view。

要自己定义CoordinatorLayout Behavior,你需要实现layoutDependsOn()和onDependentViewChanged()两个方法。比如APPBarLayout.Behavior就定义了这两个关键方法。这个behavior用于当滚动发生的时候让APPBarLayout发生改变。


eg:Android CoordinatorLayout+AppBarLayout

向上滚动隐藏指定的View

实现效果:向下滚动RecyclerView,Tab会被隐藏,向上滚动RecyclerView,Tab恢复出现,这么做的好处在于,用户能有更多的空间位置去看列表里面的内容。

<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">


<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/third_activity_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"/>

    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_scrollFlags="scroll|enterAlways"
        app:tabIndicatorColor="@color/medium_blue"
        app:tabSelectedTextColor="@color/medium_blue"
        app:tabTextAppearance="@style/TabText"
        app:tabTextColor="@color/gray_text"/>

</android.support.design.widget.AppBarLayout>

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>

1)首先需要用CoordinatorLayout包住AppBarLayout;
2)顶部区域的view都放在AppBarLayout里面
3)AppBarLayout外面,CoordinatorLayout里面,放一个带有可滚动的View,如上的例子,放的是一个ViewPager,而ViewPager里面放了RecyclerView的,即是可以滚动的View。
4)在AppBarLayout里面的View,通过app:layout_scrollFlags属性来控制,滚动的时候表现。其中有4中Flag的类型。


  • scroll: this flag should be set for all views that want to scroll off the screen - for views that do not use this flag, they’ll remain pinned to the top of the screen
  • enterAlways: this flag ensures that any downward scroll will cause this view to become visible, enabling the ‘quick return’ pattern
  • enterAlwaysCollapsed: When your view has declared a minHeight and you use this flag, your View will only enter at its minimum height (i.e., ‘collapsed’), only re-expanding to its full height when the scrolling view has reached it’s top.
  • exitUntilCollapsed: this flag causes the view to scroll off until it is ‘collapsed’ (its minHeight) before exiting

上面的例子种用的是 **scroll enterAlways.
** Scroll
表示向下滚动时,这个View会被滚出屏幕范围直到隐藏.
enterAlways 表示向上滚动时,这个View会随着滚动手势出现,直到恢复原来的位置.

  1. 在可以滚动的View上设置属性app:layout_behavior.
    该属性的值实际上是一个完整的class名字,而上面例子中的 @string/appbar_scrolling_view_behavior 是Android Support Library 定义后的值,可以被直接使用.
    这个Behavior的class是真正控制滚动时候View的滚动行为.我们也可以继承Behavior这个class去实现特有的滚动行为.
    6)代码部分,只需要实现RecyclerView的逻辑就可以了。

CorordinatorLayout高级用法--自定义Behavior

在新的support design中,CoordinatorLayout可以说是最重要的一个控件了,CoordinatorLayout给我们带来了一种新的处理方式--behavior,
eg:在使用CoordinatorLayout的时候,一些子View需要一段
app:layout_behavior=@"string/appbar_scrolling_view_behavior"
这样的xml配置?其实这是一个类,而且还可以自定义!

认识behavior

behavior是CoordinatorLayout的一个抽象内部类

public abstract static class Behavior {
public Behavior(){
}

public Behavior(Context context, AttributeSet attrs) {
}
...
}

有一个泛型是指定我们应用这个Behavior的view的类型,例如上面的appbar_scrolling_view_behavior对应的字符串其实是android.support.design.widget.AppBarLayout$ScrollingViewBehavior,这个ScrollingViewBehavior内部类指定的泛型是View,所以理论上这个Behavior我们任何的View都可以使用,我们在自定义的时候,如果不是特殊的行为,也可以直接指定泛型View。

在自定义Behavior的时候,我们需要关心的两组四个方法。

某个View监听另一个view的状态变化,例如大小、位置、显示状态等
某个view监听CoordinatorLayout里的滑动状态

对于第一种情况,我们关心的是:
layoutDependendsOn和onDependentViewChanged方法;
对于第二种情况,我们关心的是:
onStartNestedScroll和onNestedPreScroll方法。


Android Support Design 中 CoordinatorLayout 与 Behaviors 初探

CoordinatorLayout默认情况下可以理解是一个FrameLayout,它的布局方式默认是一层一层叠上去。

CoordinatorLayout的神奇之处就在于Behavior对象了。Behavior对象是用来给CoordinatorLayout的子view们进行交互用的。

Behavior接口拥有很多个方法,我们拿AppBarLayout为例。AppBarLayout中有两个Behavior,一个是拿来给他自己用的,另一个是拿来给它的兄弟结点用的,我们重点关注下AppBarLayout.ScrollingViewBehavior这个类。


eg Android CollapsingToolbarLayout

大的Banner图,能第一时间吸引用户的眼球,用不一样的Banner大图更具个性化的展示内容.图总是比文字要吸引人.
当向下滚动时,Banner大图会跟随滚动手势而Collapse.最后收折成一个普通的ActionBar(实际是个Toolbar,Android官方在最新的Support Library都推荐把ActionBar替换成Toolbar).
通过属性Flag的组合,也能实现把ActionBar直接推出屏幕,让其消失

<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsing_toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        app:contentScrim="?attr/colorPrimary"
        app:expandedTitleMarginEnd="64dp"
        app:expandedTitleMarginStart="48dp"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

        <ImageView
            android:id="@+id/backdrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_collapseMode="parallax"
            android:scaleType="centerCrop"
            android:src="@drawable/mu"
            android:transitionName="mu"/>

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_collapseMode="pin"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:theme="@style/MyToolbarTheme"/>

    </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<android.support.v7.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

这是Layout布局.CoordinatorLayout和AppBarLayout的组合在这篇随笔中有介绍,实现了滚动隐藏Toolbar的效果,这里就不在重复了.

CollapsingToolbarLayout是实现GIF效果的关键.

CollapsingToolbarLayout有两个Children.ImageView用来显示Banner大图,即Gif中曼联队徽的大图.而Toolbar就是折叠后看到的顶栏Toolbar.

app:contentScrim="?attr/colorPrimary",CollapsingToolbarLayout这个属性是设置折叠后Toolbar的颜色.

app:layout_scrollFlags="scroll|exitUntilCollapsed",这是两个Flag控制滚动时候CollapsingToolbarLayout的表现.

  1. **Scroll, **表示向下滚动列表时候,CollapsingToolbarLayout会滚出屏幕并且消失(原文解释:this flag should be set for all views that want to scroll off the screen - for views that do not use this flag, they’ll remain pinned to the top of the screen)
  1. **exitUntilCollapsed, **表示这个layout会一直滚动离开屏幕范围,直到它收折成它的最小高度.(原文解释:this flag causes the view to scroll off until it is ‘collapsed’ (its minHeight) before exiting)

app:layout_collapseMode="parallax",这是控制滚出屏幕范围的效果的
1) parallax,表示滚动过程中,会一直保持可见区域在正中间.
2) pin,表示不会被滚出屏幕范围.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.fourth_activity);

    final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    ActionBar actionBar = getSupportActionBar();
    if (actionBar != null) {
        actionBar.setDisplayHomeAsUpEnabled(true);
    }

    final CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(
            R.id.collapsing_toolbar);
    collapsingToolbar.setTitle(getString(R.string.fourth_activity));

    final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(linearLayoutManager);
    recyclerView.setAdapter(new MyAdapter(this));

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mu);
    Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
        @Override
        public void onGenerated(final Palette palette) {
            int defaultColor = getResources().getColor(R.color.medium_blue);
            int defaultTitleColor = getResources().getColor(R.color.white);
            int bgColor = palette.getDarkVibrantColor(defaultColor);
            int titleColor = palette.getLightVibrantColor(defaultTitleColor);

            collapsingToolbar.setContentScrimColor(bgColor);
            collapsingToolbar.setCollapsedTitleTextColor(titleColor);
            collapsingToolbar.setExpandedTitleColor(titleColor);
        }
    });
    }

这是Activity的onCreate方法,有两处地方需要关注的

  1. setSupportActionBar()方法,告诉AppCompatActivity哪一个是ActionBar(实际是Toolbar).不然的话,做透明Status Bar(电池,手机信号那一区域)效果时候,ActionBar会位置不正确.
  2. Palette,调色板的意思,也是Android Support Library提供的.用来抓取Bitmap的颜色.在此处的用处是,当ActionBar被收折后,背景颜色能保持和Banner大图的色调一致,而Title文字的颜色保证和Banner大图的色调形成强对比.

Android Design Support Library简介

AppBarLayout

AppBarLayout是继承LinearLayout实现的一个ViewGroup容器组件,它是为了Material Design设计的App Bar,支持手势滑动操作。

默认的AppBarLayout是垂直方向的,它的作用是把AppBarLayout包裹的内容都作为AppBar.

此处将Toolbar和Tablayout组合部分共同构成AppBar的效果。

注意:AppBarLayout必须作为Toolbar的父布局容器。

AppBarLayout是支持手势滑动效果的,不过要跟CoordinatorLayout配合使用。

CoordinatorLayout

CoordinatorLayout是一个增强型的FrameLayout。它的作用有两个,作为一个布局的根布局。子视图之间相互协调手势效果的一个协调布局。

CoordinatorLayout作为“super-powered FrameLayout”基本实现两个功能:
1、作为顶层布局
2、调度协调子布局

CoordinatorLayout使用新的思路通过协调调度子布局的形式实现触摸影响布局的形式产生动画效果。CoordinatorLayout通过设置子View的 Behaviors来调度子View。系统(Support V7)提供了AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior, FloatingActionButton.Behavior, SwipeDismissBehavior<V extends View> 等。

总结: 为了使得Toolbar有滑动效果,必须做到如下三点:
CoordinatorLayout必须作为整个布局的父布局容器。 给需要滑动的组件设置 app:layout_scrollFlags=”scroll|enterAlways” 属性。 给你的可滑动的组件,也就是RecyclerView 或者 NestedScrollView 设置如下属性:
app:layout_behavior = @string/appbar_scrolling_view_behavior

3.给需要有折叠效果的组件设置 layout_collapseMode属性。

NavigationView

用于侧滑菜单中的menu布局。之前Google在V4包中推出自己的 DrawerLayout作为抽屉侧滑菜单。其实这次谷歌只是将上面的ListView布局替换成NavigationView了。简化了之前ListView写适配器的繁琐。

这里最主要的两个属性分别是:
1.app:headerLayout: 给NavigationView添加头部布局2.app:menu:给NavigationView添加menu菜单布局

Snackbar

Snackbar提供了一个介于Toast和AlertDialog之间轻量级控件,它可以很方便的提供消息提示和动作反馈。

TextInputLayout

TextInputLayout作为一个父容器控件,包装了新的EditText。通常,单独的EditText会在用户输入第一个字母之后隐藏hint提示信息,但是现在你可以使用TextInputLayout 来将EditText封装起来,提示信息会变成一个显示在EditText之上的floating label,这样用户就始终知道他们现在输入的是什么。同时,如果给EditText增加监听,还可以给它增加更多的floating label。

Floating Action Button

floating action button 是一个负责显示界面基本操作的圆形按钮。Design library中的FloatingActionButton 实现了一个默认颜色为主题中colorAccent的悬浮操作按钮

FloatingActionButton继承自ImageView,你可以使用android:src或者ImageView的任意方法,比如setImageDrawable()来设置FloatingActionButton里面的图标。

TabLayout

Design library的TabLayout 既实现了固定的选项卡 - view的宽度平均分配,也实现了可滚动的选项卡 - view宽度不固定同时可以横向滚动。选项卡可以在程序中动态添加:

但大部分时间我们都不会这样用,通常滑动布局都会和ViewPager配合起来使用,所以,我们需要ViewPager来帮忙:

通过一句话setupWithViewPager,我们就把ViewPager和TabLayout结合了起来。

推荐阅读更多精彩内容