Android Material Design 系列之 CoordinatorLayout + CollapsingToolbarLayout 开发详解

前言

CoordinatorLayout 遵循 Material 风格,结合 AppbarLayout, CollapsingToolbarLayout 等可产生各种炫酷的效果,本篇博客就将介绍 CoordinatorLayout 的各种酷炫效果。

一、View 介绍

1、CoordinatorLayout

又名协调者布局,它是 support.design 包中的控件。简单来说,CoordinatorLayout 是用来协调其子 view 并以触摸影响布局的形式产生动画效果的一个 super-powered FrameLayout,其典型的子 View 包括:FloatingActionButtonSnackBar。注意:CoordinatorLayout 是一个顶级父 View

2、AppBarLayout

AppBarLayoutLinearLayout 的子类,必须在它的子 view 上设置 app:layout_scrollFlags 属性或者是在代码中调用 setScrollFlags()设置这个属性。

AppBarLayout 的子布局有 5 种滚动标识(上面代码 CollapsingToolbarLayout 中配置的 app:layout_scrollFlags 属性):

  • scroll:所有想滚动出屏幕的 view 都需要设置这个 flag, 没有设置这个 flag 的 view 将被固定在屏幕顶部。
  • enterAlways:这个 flag 让任意向下的滚动都会导致该 view 变为可见,启用快速“返回模式”。
  • enterAlwaysCollapsed:假设你定义了一个最小高度(minHeight)同时 enterAlways 也定义了,那么 view 将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完。
  • exitUntilCollapsed:当你定义了一个 minHeight,此布局将在滚动到达这个最小高度的时候折叠。
  • snap:当一个滚动事件结束,如果视图是部分可见的,那么它将被滚动到收缩或展开。例如,如果视图只有底部 25%显示,它将折叠。相反,如果它的底部 75%可见,那么它将完全展开。

3、CollapsingToolbarLayout

CollapsingToolbarLayout 作用是提供了一个可以折叠的 Toolbar,它继承自 FrameLayout,给它设置 layout_scrollFlags,它可以控制包含在 CollapsingToolbarLayout 中的控件(如:ImageView、Toolbar)在响应 layout_behavior 事件时作出相应的 scrollFlags 滚动事件(移除屏幕或固定在屏幕顶端)。

CollapsingToolbarLayout 可以通过 app:contentScrim 设置折叠时工具栏布局的颜色,通过 app:statusBarScrim 设置折叠时状态栏的颜色。默认 contentScrimcolorPrimary 的色值,statusBarScrimcolorPrimaryDark 的色值。

CollapsingToolbarLayout 的子布局有 3 种折叠模式(Toolbar 中设置的 app:layout_collapseMode

  • off:默认属性,布局将正常显示,无折叠行为。
  • pin:CollapsingToolbarLayout 折叠后,此布局将固定在顶部。
  • parallax:CollapsingToolbarLayout 折叠时,此布局也会有视差折叠效果。
    当 CollapsingToolbarLayout 的子布局设置了 parallax 模式时,我们还可以通过 app:layout_collapseParallaxMultiplier 设置视差滚动因子,值为:0~1。

常用属性:

  • app:contentScrim:当 Toolbar 收缩到一定程度时的所展现的主体颜色。即 Toolbar 的颜色。
  • app:title:当 titleEnable 设置为 true 的时候,在 toolbar 展开的时候,显示大标题,toolbar 收缩时,显示为 toolbar 上面的小标题。
  • app:scrimAnimationDuration:该属性控制 toolbar 收缩时,颜色变化的动画持续时间。即颜色变为 contentScrim 所指定的颜色进行的动画所需要的时间
  • app:collapsedTitleTextAppearance:指定 toolbar 收缩时,标题字体的样式。
  • app:collapsedTitleGravity : 指定 toolbar 收缩时的标题文字对齐方式。
  • app:expandedTitleTextAppearance : 指定 toolbar 展开后的标题文字字体。
  • app:expandedTitleGravity:指定 toolbar 展开时,title 所在的位置。类似的还有 expandedTitleMargin、collapsedTitleGravity 这些属性。
  • app:expandedTitleMargin : 指定展开后的标题四周间距。

4、NestedScrollView

在新版的 support-v4 兼容包里面有一个 NestedScrollView 控件,这个控件其实和普通的 ScrollView 并没有多大的区别,这个控件其实是 Meterial Design 中设计的一个控件,目的是跟 MD 中的其他控件兼容。应该说在 MD 中,RecyclerView 代替了 ListView,而 NestedScrollView 代替了 ScrollView,他们两个都可以用来跟 ToolBar 交互,实现上拉下滑中 ToolBar 的变化。在 NestedScrollView 的名字中其实就可以看出他的作用了,Nested 是嵌套的意思,而 ToolBar 基本需要嵌套使用。

5、FloatingActionButton

FloatingActionButton 就是一个漂亮的按钮,其本质是一个 ImageVeiw。有一点要注意,Meterial Design 引入了 Z 轴的概念,就是所有的 view 都有了高度,他们一层一层贴在手机屏幕上,而 FloatingActionButton 的 Z 轴高度最高,它贴在所有 view 的最上面,没有 view 能覆盖它。

6、Behavior

Behavior 只有是 CoordinatorLayout 的直接子 View 才有意义。只要将 Behavior 绑定到 CoordinatorLayout 的直接子元素上,就能对触摸事件(touch events)、window insets、measurement、layout 以及嵌套滚动(nested scrolling)等动作进行拦截。Design Library 的大多功能都是借助 Behavior 的大量运用来实现的。当然,Behavior 无法独立完成工作,必须与实际调用的 CoordinatorLayout 子视图相绑定。具体有三种方式:通过代码绑定、在 XML 中绑定或者通过注释实现自动绑定

二、应用实战

1、CoordinatorLayout + FloatingActionButton 使用

市面上众多 APP 首页都会使用 CoordinatorLayout 结合 FloatingActionButton 的效果,当列表垂直方向滑动时,FloatingActionButton 随之显示与隐藏。

  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:background="@color/white">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="?attr/actionBarSize"
        android:clipChildren="false"
        android:clipToPadding="false"/>

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        app:titleTextColor="@color/white"
        tools:ignore="MissingConstraints" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:onClick="fabClick"
        app:layout_behavior=".coordinatorLayout.behavior.FabBehavior"
        app:rippleColor="@color/colorPrimaryDark"
        app:backgroundTint="@color/colorPrimary"
        android:id="@+id/fab"
        android:clickable="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_margin="20dp"
        app:fabSize="auto"
        android:focusable="true" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

可以看到布局文件最外层使用 CoordinatorLayout,里面包含了 RecyclerViewToolbarFloatingActionButton
三个子 View,使用android:layout_gravity="end|bottom"属性设置 FloatingActionButton 显示在右下角位置。关于 ToolBarFloatingActionButton 使用还不熟悉的朋友请自行补课。

FloatingActionButton 设置app:layout_behavior属性,这里的".coordinatorLayout.behavior.FabBehavior"是自定义 Behavior 实现。

  • 自定义 Behavior
public class FabBehavior extends FloatingActionButton.Behavior {

    private boolean visible = true;

    public FabBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull FloatingActionButton child,
                                       @NonNull View directTargetChild,
                                       @NonNull View target,
                                       int axes, int type) {
        //判断如果是垂直滚动则返回true
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                               @NonNull FloatingActionButton child,
                               @NonNull View target,
                               int dxConsumed, int dyConsumed,
                               int dxUnconsumed, int dyUnconsumed, int type) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);

        if (dyConsumed > 0 && visible) {
            visible = false;
            child.animate().scaleX(0).scaleY(0).setInterpolator(new AccelerateInterpolator(3));
        } else if (dyConsumed < 0 && !visible) {
            visible = true;
            child.animate().scaleX(1).scaleY(1).setInterpolator(new DecelerateInterpolator(3));
        }
    }
}
  • onStartNestedScroll:一定要按照自己的需求返回 true,该方法决定了当前控件是否能接收到其内部 View(非并非是直接子 View)滑动时的参数;假设你只涉及到纵向滑动,这里可以根据 nestedScrollAxes 这个参数,进行纵向判断。
  • onNestedScroll:在内层 view 将剩下的滚动消耗完之后调用,可以在这里处理最后剩下的滚动

2、CoordinatorLayout + FloatingActionButton + Snackbar 使用

日常开发过程中,使用 FloatingActionButton + Snackbar 会出现一个 BUG,当 Snackbar 显示时会覆盖 FloatingActionButton 显示,这种展示效果很不友好,如下图所示。

解决问题最简单的方式就是最外层布局更换成 CoordinatorLayout,即可轻松完成

  • Activity 类:(提醒:布局文件与上面一致)
public class SnackBarBehaviorActivity extends BaseActivity {

    @BindView(R.id.recyclerView)
    RecyclerView recyclerView;
    RecyclerViewAdapter recyclerViewAdapter;
    private List<String> stringList = new ArrayList<>();

    @Override
    protected void initView() {
        setToolbarTitle("SnackBar Behavior");
        setToolBarCallBack();
        for (int i = 1; i <= 100; i++) {
            stringList.add("ITEM " + i);
        }
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        recyclerViewAdapter = new RecyclerViewAdapter(R.layout.recyclerview_item, stringList);
        recyclerViewAdapter.bindToRecyclerView(recyclerView);
    }

    @Override
    protected int getLayoutResID() {
        return R.layout.activity_fabbehavior_layout;
    }

    public void fabClick(View view) {
        Snackbar.make(view, "谁让你点击的?", Snackbar.LENGTH_SHORT)
                .setAction("关闭", v -> {
                    Toast.makeText(SnackBarBehaviorActivity.this, "Sorry", Toast.LENGTH_SHORT).show();
                }).addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
            @Override
            public void onDismissed(Snackbar transientBottomBar, int event) {
                super.onDismissed(transientBottomBar, event);
            }

            @Override
            public void onShown(Snackbar transientBottomBar) {
                super.onShown(transientBottomBar);
            }
        }).show();
    }
}

其实代码很简单,按照正常逻辑处理就完事了,主要在于布局文件的合理搭配使用上。

3、CoordinatorLayout + AppBarLayout 使用

CoordinatorLayout + AppBarLayout 应该是现在 APP 主流的布局方式,配合 ToolbarTabLayout 可以实现炫酷的列表滑动折叠效果,提升应用整体档次,增加用户体验。

  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:titleTextColor="@color/white"
            tools:ignore="MissingConstraints" />

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.appbar.AppBarLayout>


    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewPage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

使用 AppBarLayoutToolbarTabLayout 包裹,底部使用 ViewPager 布局,ViewPagerapp:layout_behavior=”@string/appbar_scrolling_view_behavior”Behavior 是系统默认的,我们也可以根据自己的需求来自定义 Behavior

注意 Toolbar 设置了 app:layout_scrollFlags="scroll|enterAlways"属性,这是一个非常重要的属性,子布局通过此确定是否可滑动。 其中有五种属性,具体含义可阅读上文 AppBarLayout 介绍。

  • Activity 类
public class CoordinatorCardViewActivity extends BaseActivity {
    private List<Fragment> fragments = new ArrayList<>();
    @BindView(R.id.tabLayout)
    TabLayout tabLayout;
    @BindView(R.id.viewPage)
    ViewPager viewPage;
    private String mTitles[] = {"TAB 0", "TAB 1", "TAB 2"};

    @Override
    protected void initView() {
        setToolBarCallBack();
        setToolbarTitle("Behavior");
        // 设置文本字体颜色[未选中颜色、选中颜色]
        tabLayout.setTabTextColors(getResources().getColor(R.color.gray),
                getResources().getColor(R.color.white));
        // 设置下划线跟文本宽度一致
        tabLayout.setTabIndicatorFullWidth(true);
        // 设置TabLayout和ViewPager绑定
        tabLayout.setupWithViewPager(viewPage, false);
        // 添加TAB标签
        for (String mTitle : mTitles) {
            tabLayout.addTab(tabLayout.newTab().setText(mTitle));
        }
        fragments.add(CardImageFragment.newInstance());
        fragments.add(CardTextFragment.newInstance());
        fragments.add(CardBelleFragment.newInstance());

        viewPage.setAdapter(new FragmentAdapter(getSupportFragmentManager(), tabLayout.getTabCount()));
        // 设置ViewPager默认显示index
        viewPage.setCurrentItem(0);
    }

    @Override
    protected int getLayoutResID() {
        return R.layout.activity_coordinator_cardviewlayout;
    }

    class FragmentAdapter extends FragmentPagerAdapter {

        public FragmentAdapter(@NonNull FragmentManager fm, int behavior) {
            super(fm, behavior);
        }

        @Nullable
        @Override
        public CharSequence getPageTitle(int position) {
            return mTitles[position];
        }

        @NonNull
        @Override
        public Fragment getItem(int position) {
            return fragments.get(position);
        }

        @Override
        public int getCount() {
            return fragments.size();
        }
    }

}

Activity 类主要设置了 TabLayoutViewPager 相关属性,如果对 TabLayout + ViewPager 还不熟悉的朋友可以阅读之前文章。实现具体效果如下:

4、CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout 使用

  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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">

    <com.google.android.material.appbar.AppBarLayout
        android:fitsSystemWindows="true"
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            app:title="Android"
            app:titleEnabled="true"
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.8"
                app:layout_scrollFlags="scroll|snap|enterAlways|enterAlwaysCollapsed"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@mipmap/android" />

            <androidx.appcompat.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" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Design for Android"
            android:textColor="@color/white"
            android:textStyle="bold" />

    </androidx.core.widget.NestedScrollView>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@android:drawable/ic_dialog_email"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

FloatingActionButton 这个控件通过 app:layout_anchor 这个设置锚定在了 AppBarLayout 下方。FloatingActionButton 源码中有一个 Behavior 方法,当 AppBarLayout 收缩时,FloatingActionButton 就会跟着做出相应变化。

  • Activity 类
public class AppBarBehaviorActivity extends BaseActivity {

    @Override
    protected void initView() {
        StatusBarUtils.setTransparent(this);
        setToolBarCallBack();
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    @Override
    protected int getLayoutResID() {
        return R.layout.activity_coordinator_collapsinglayout;
    }
}

注意:在背景图片沉浸式的时候,只靠上面的 XML 布局是无法实现的,还需要 2 行代码:

setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

三、总结

本文主要介绍 CoordinatorLayout 的基本使用,还是非常简单的,CoordinatorLayout 作为协调者,肯定是非常重要的。CoordinatorLayout 是一个“加强版”的 FrameLayout,我们只需要知道他两个作用

(1) 用作应用的顶层布局管理器

(2) 通过为子 View 指定 behavior 实现自定义的交互行为。 在我们做 Material Design 风格的 app 时通常都使用 CoordinatorLayout 作为布局的根节点,以便实现特定的 UI 交互行为。

我的微信:Jaynm888

欢迎点评,诚邀 Android 程序员加入微信交流群,公众号回复加群或者加我微信邀请入群。

猜你喜欢

转载自blog.csdn.net/jaynm/article/details/107640690
今日推荐