前言
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 交互行为。