前言
CoordinatorLayout 遵循 Material 风格,结合 AppbarLayout,CollapsingToolbarLayout 等可产生各种炫酷的效果,本篇博客就将介绍 CoordinatorLayout 的各种酷炫效果。
一、View 介绍
1、CoordinatorLayout
又名协调者布局,它是 support.design 包中的控件。简单来说,CoordinatorLayout 是用来协调其子 view 并以触摸影响布局的形式产生动画效果的一个 super-powered FrameLayout,其典型的子 View 包括:FloatingActionButton,SnackBar。注意:CoordinatorLayout 是一个顶级父 View。
2、AppBarLayout
AppBarLayout 是 LinearLayout 的子类,必须在它的子 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 设置折叠时状态栏的颜色。默认 contentScrim 是 colorPrimary 的色值,statusBarScrim 是 colorPrimaryDark 的色值。
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,里面包含了 RecyclerView、Toolbar、FloatingActionButton三个子 View,使用android:layout_gravity="end|bottom"属性设置 FloatingActionButton 显示在右下角位置。关于 ToolBar 与 FloatingActionButton 使用还不熟悉的朋友请自行补课。
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 主流的布局方式,配合 Toolbar、TabLayout 可以实现炫酷的列表滑动折叠效果,提升应用整体档次,增加用户体验。
-
布局文件
<?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>` </pre>
使用 AppBarLayout 将 Toolbar 和 TabLayout 包裹,底部使用 ViewPager 布局,ViewPager 中 app: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 类主要设置了 TabLayout 与 ViewPager 相关属性,如果对 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>` </pre>
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 交互行为。