首先我们得知道 CoordinatorLayout 是什么玩意儿,到底有什么用,我们不妨看看官方文档的描述:
CoordinatorLayout 是一个 “加强版” FrameLayout, 它主要有两个用途:
其实总结出来就是 CoordinatorLayout 是一个布局管理器,相当于一个增强版的 FrameLayout,但是它神奇在于可以实现它的子 View 之间的交互行为。
可能大家看到这,就自然能想到观察者模式,或者我前面写的Rx模式:这可能是最好的RxJava 2.x 教程(完结版)
我们的 Button 就是一个被观察者,TextView 作为一个观察者,当 Button 移动的时候通知 TextView, TextView 就跟着移动。看看其布局:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nanchen.coordinatorlayoutdemo.CoordinatorActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="观察者"
app:layout_behavior=".FollowBehavior"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="被观察者"
android:layout_gravity="center"
android:id="@+id/btn"/>
android.support.design.widget.CoordinatorLayout>
很简单,一个 TextView, 一个 Button, 外层用 CoordinatorLayout, 然后给我们的 TextView 加一个自定义的 Behavior 文件。
自定义 Behavior:
重写layoutDependOn
和onDependentViewChanged
/* 自定义 CoordinatorLayout 的 Behavior, 泛型为观察者 View ( 要跟着别人动的那个 )
* 必须重写两个方法,layoutDependOn和onDependentViewChanged */
public class FollowBehavior extends CoordinatorLayout.Behavior<TextView>{
// 构造方法
public FollowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/* 判断child的布局是否依赖 dependency
* 根据逻辑来判断返回值,返回 false 表示不依赖,返回 true 表示依赖
*
* 在一个交互行为中,Dependent View 的变化决定了另一个相关 View 的行为。
* 在这个例子中, Button 就是 Dependent View,因为 TextView 跟随着它。
* 实际上 Dependent View 就相当于我们前面介绍的被观察者 */
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof Button;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
child.setX(dependency.getX());
child.setY(dependency.getY() + 100);
return true;
}
}
重点看看其中重写的两个方法 layoutDependsOn() 和 onDependentViewChanged() 。
在介绍这两个方法的作用前,我们先来介绍一下 Dependent View。在一个交互行为中,Dependent View 的变化决定了另一个相关 View 的行为。在这个例子中, Button 就是 Dependent View, 因为 TextView 跟随着它。实际上 Dependent View 就相当于我们前面介绍的被观察者。
知道了这个概念,让我们看看重写的两个方法的作用:
所以我们现在可以开始写Activity中的代码:
findViewById(R.id.btn).setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_MOVE){
view.setX(motionEvent.getRawX()-view.getWidth()/2);
view.setY(motionEvent.getRawY()-view.getHeight()/2);
}
return true;
}
});
这样一来,我们就完成了为 TextView 和Button 设置跟随移动这个交互行为。
其实为 CoordinatorLayout 的子 View 设置交互行为只需三步:
值得注意的是,有些时候,并不需要我们自己来定义一个 Behavior 类,因为系统为我们预定义了不少 Behavior 类。
现在我们已经知道了怎么通过给 CoordinatorLayout 的子 View 设置 Behavior 来实现交互行为。
实际上, CoordinatorLayout 本身并没有做过多工作,实现交互行为的主要幕后推手是 CoordinatorLayout 的内部类—— Behavior。通过为 CoordinatorLayout 的直接子 View 绑定一个 Behavior ,这个 Behavior 就会拦截发生在这个 View 上的 Touch 事件、嵌套滚动等。不仅如此,Behavior 还能拦截对与它绑定的 View 的测量及布局。
当我们为一个 CoordinatorLayout 的直接子 View 设置了 Behavior 时,这个 Behavior 就能拦截发生在这个 View 上的 Touch 事件,那么它是如何做到的呢?
实际上, CoordinatorLayout 重写了 onInterceptTouchEvent() 方法,并在其中给 Behavior 开了个后门,让它能够先于 View 本身处理 Touch 事件。
具体来说, CoordinatorLayout 的 onInterceptTouchEvent() 方法中会遍历所有直接子 View ,对于绑定了 Behavior 的直接子 View 调用 Behavior 的 onInterceptTouchEvent() 方法,若这个方法返回 true, 那么后续本该由相应子 View 处理的 Touch 事件都会交由 Behavior 处理,而 View 本身表示懵逼,完全不知道发生了什么。
CoordinatorLayout 重写了测量及布局相关的方法并为 Behavior 开了个后门。
CoordinatorLayout 在 onMeasure() 方法中,会遍历所有直接子 View ,若该子 View 绑定了一个 Behavior ,就会调用相应 Behavior 的 onMeasureChild() 方法,若此方法返回 true,那么 CoordinatorLayout 对该子 View 的测量就不会进行。这样一来, Behavior 就成功接管了对 View 的测量。
同样,CoordinatorLayout 在 onLayout() 方法中也做了与 onMeasure() 方法中相似的事,让 Behavior 能够接管对相关子 View 的布局。
现在,我们在探究一下交互行为中的两个 View 之间的依赖关系是怎么确定的。我们称 child 为交互行为中根据另一个 View 的变化做出响应的那个个体,而 Dependent View 为child所依赖的 View。
实际上,确立 child 和 Dependent View 的依赖关系有两种方式:
无论是隐式依赖还是显式依赖,在 Dependent View 发生变化时,相应 Behavior 类的 onDependentViewChanged() 方法都会被调用,在这个方法中,我们可以让 child 做出改变以响应 Dependent View 的变化。
实际上我们在应用中有 CoordinatorLayout 的地方通常都会有 AppBarLayout 的联用,作为同样的出自 Design 包的库,我们看看官方文档怎么说:
AppBarLayout 是一个垂直的 LinearLayout,实现了 Material Design 中 App bar 的 Scrolling Gestures 特性。AppBarLayout 的子 View 应该声明想要具有的“滚动行为”,这可以通过 layout_scrollFlags 属性或是 setScrollFlags() 方法来指定。
AppBarLayout 只有作为 CoordinatorLayout 的直接子 View 时才能正常工作,为了让 AppBarLayout 能够知道何时滚动其子 View,我们还应该在 CoordinatorLayout 布局中提供一个可滚动 View,我们称之为 Scrolling View。
Scrolling View 和 AppBarLayout 之间的关联,通过将 Scrolling View 的 Behavior 设为 AppBarLayout.ScrollingViewBehavior 来建立。
AppBar 是 Design 的一个概念,其实我们也可以把它看做一种 5.0 出的 ToolBar,先感受一下 AppBarLayout + CoordinatorLayout 的魅力。
实际效果就是这样,当向上滑动 View 的时候,ToolBar 会小时,向下滑动的时候,ToolBar 又会出现,但别忘了,这是 AppBarLayout 的功能,ToolBar 可办不到。由于要滑动,那么我们的 AppBarLayout 一定是和可以滑动的 View 一起使用的,比如 RecyclerView,NestedScrollView等。
我们看看上面的到底怎么实现的:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_coor_app_bar"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nanchen.coordinatorlayoutdemo.CoorAppBarActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<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.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
android.support.design.widget.CoordinatorLayout>
我们可以看到,上面出现了一个 app:layouy_scrollFrags 的自定义属性设置,这个属性可以定义我们不同的滚动行为。
根据官方文档,layout_scrollFlags 的取值可以为以下几种。
设成这个值的效果就好比本 View 和 Scrolling view 是“一体”的。具体示例我们在上面已经给出。有一点特别需要我们的注意,为了其他的滚动行为生效,必须同时指定 Scroll 和相应的标记,比如我们想要 exitUntilCollapsed 所表现的滚动行为,必须将 layout_scrollFlags 指定为 scroll|exitUntilCollapsed 。
当本 View 离开屏幕时,会被“折叠”直到达到其最小高度。我们可以这样理解这个效果:当我们开始向上滚动 Scrolling view 时,本 View 会先接管滚动事件,这样本 View 会先进行滚动,直到滚动到了最小高度(折叠了),Scrolling view 才开始实际滚动。而当本 View 已完全折叠后,再向下滚动 Scrolling view,直到 Scrolling view 顶部的内容完全显示后,本 View 才会开始向下滚动以显现出来。
当 Scrolling view 向下滚动时,本 View 会一起跟着向下滚动。实际上就好比我们同时对 Scrolling view 和本 View 进行向下滚动。
从名字上就可以看出,这是在 enterAlways 的基础上,加上了“折叠”的效果。当我们开始向下滚动 Scrolling View 时,本 View 会一起跟着滚动直到达到其“折叠高度”(即最小高度)。然后当 Scrolling View 滚动至顶部内容完全显示后,再向下滚动 Scrolling View,本 View 会继续滚动到完全显示出来。
在一次滚动结束时,本 View 很可能只处于“部分显示”的状态,加上这个标记能够达到“要么完全隐藏,要么完全显示”的效果。
这个东西,我相信很多博客和技术文章都会把 CollapsingToolBarLayout 和 CoordinatorLayout 放一起讲,这个东西的确很牛。我们同样先看看官方文档介绍:
CollapsingToolbarLayout 通常用来在布局中包裹一个 Toolbar,以实现具有“折叠效果“”的顶部栏。它需要是 AppBarLayout 的直接子 View,这样才能发挥出效果。
CollapsingToolbarLayout包含以下特性:
上面的描述有些抽象,实际上对于 Content scrim 、Status bar scrim 我们可以暂时予以忽略,只要留个大概印象待以后需要时再查阅相关资料即可。下面我们通过一个常见的例子介绍下 CollapsingToolbarLayout 的基本使用姿势。
我们来看看一个常用的效果:
看看布局:
<android.support.design.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"
tools:context="cn.rt.rtcoordinatorlayout.CollapsingToolBarActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:fitsSystemWindows="true"
app:collapsedTitleGravity="top"
app:collapsedTitleTextAppearance="@style/TextAppearance.Design.CollapsingToolbar.Expanded"
app:contentScrim="@color/app_color"
app:expandedTitleGravity="left|bottom"
app:expandedTitleMarginStart="100dp"
app:expandedTitleTextAppearance="@style/TextAppearance.Design.CollapsingToolbar.Expanded"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:statusBarScrim="@color/green"
app:title="大大大折叠">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.6" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:fitsSystemWindows="true"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="折叠" />
android.support.design.widget.CollapsingToolbarLayout>
android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|right"
app:srcCompat="@android:drawable/ic_dialog_email" />
android.support.design.widget.CoordinatorLayout>
我们在 XML 文件中为 CollapsingToolBarLayout 的 layout_scrollFlags 指定为 scroll|exitUntilCollapsed|snap,这样便实现了向上滚动的折叠效果。
CollapsingToolbarLayout 本质上同样是一个 FrameLayout,我们在布局文件中指定了一个 ImageView 和一个 Toolbar。
ImageView 的layout_collapseMode 属性设为了 parallax,也就是我们前面介绍的视差滚动;
而 Toolbar 的 layout_collaspeMode 设为了 pin ,也就是 Toolbar 会始终固定在顶部。
今天的效果在支付宝、淘宝、京东等电商App中很常见。比如支付宝输入密码弹窗、商城下单时选择商品属性时,从下面浮动上来一个PopupWindow
,那么今天就带大家用Behavior
来实现这两个效果,结果你会发现简直只需要一行代码。
总结下现在用的APP:
1. 仿支付宝弹出的输入支付密码窗口。
2. 仿淘宝/天猫弹出商品属性选择框。
3. 知乎首页上下滑动隐藏ToolBar和NavigationBar。
4. …
系列博客:
<LinearLayout
...
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<Button
...
android:text="sheet 显示/隐藏" />
LinearLayout>
<LinearLayout
...
app:layout_behavior="@string/bottom_sheet_behavior">
<Button
...
android:text="第一" />
<Button
...
android:text="第二" />
<Button
...
android:text="第三" />
<Button
...
android:text="第四" />
LinearLayout>
首先Behavior
作为CoordinatorLayout
的子View的LayoutParams
(原因看后文解释),所以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:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay" />
android.support.design.widget.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/tab_layout"
android:gravity="center"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<Button
android:id="@+id/btn_bottom_sheet_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="sheet 显示/隐藏" />
LinearLayout>
<LinearLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_alignParentBottom="true"
android:background="@android:color/holo_purple"
app:layout_behavior="@string/bottom_sheet_behavior">
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第一" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第二" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第三" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="第四" />
LinearLayout>
android.support.design.widget.CoordinatorLayout>
大概介绍下,页面上只能看到Toolbar和一个Button:sheet 显示/隐藏。
然后android:id=”@+id/tab_layout”这个布局是横向的,给它设置了Behavior:app:layout_behavior=”@string/bottom_sheet_behavior”,经过测试发现,如果不给tab_layout设置BottomSheetBehavior,它会浮动在整个页面的顶部,并在Toolbar的下面。设置了BottomSheetBehavior它会被BottomSheetBehavior自动移动到页面底部外边,所以在页面上是看不到android:id=”@+id/tab_layout”这个布局的。
BottomSheetBehavior这个货有一个静态方法BottomSheetBehavior.from(View)
,会返回这个View引用的BottomSheetBehavior:
private BottomSheetBehavior mBottomSheetBehavior;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bsbehavior_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
findViewById(R.id.btn_bottom_sheet_control).setOnClickListener(onClickListener);
// 拿到这个tab_layout对应的BottomSheetBehavior
mBottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.tab_layout));
}
BottomSheetBehavior.from(View) 源码:
public static BottomSheetBehavior from(V view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new Exception("The view is not a child of CoordinatorLayout");
}
CoordinatorLayout.Behavior behavior = params.getBehavior();
if (!(behavior instanceof BottomSheetBehavior)) {
throw new IllegalArgumentException("...");
}
return (BottomSheetBehavior) behavior;
}
这个方法会检查这个View是否是CoordinatorLayout的子View,如果是才会去拿到这个View的Behavior,所以诸位也应该明白为什么我开头说Behavior作为CoordinatorLayout子View的LayoutParams了。
BottomSheetBehavior.getState()
获取状态 public static final int STATE_DRAGGING = 1;
public static final int STATE_SETTLING = 2;
public static final int STATE_EXPANDED = 3;
public static final int STATE_COLLAPSED = 4;
public static final int STATE_HIDDEN = 5;
BottomSheetBehavior.setState
设置状态 if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
new一个BottomSheetDialog
:
private BottomSheetDialog mBottomSheetDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
createBottomSheetDialog();
}
private void createBottomSheetDialog() {
mBottomSheetDialog = new BottomSheetDialog(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_bottom_sheet, null, false);
mBottomSheetDialog.setContentView(view);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
...
recyclerView.setAdapter(adapter);
}
View里面是一个RecyclerView:
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
然后用下面的代码去控制它的显示和隐藏。
BottomSheetDialog.dismiss()
BottomSheetDialog.show()
if (mBottomSheetDialog.isShowing()) {
mBottomSheetDialog.dismiss();
} else {
mBottomSheetDialog.show();
}
当这个Dialog Show出来的时候发现它显示了一半,嗯这个效果确实不错,这样就达到了我们最初说的支付宝密码弹窗和淘宝/天猫商品属性选择。
我们滑动的时候如果下面有内容它就会EXPANDED
,如果是一个普通的View(非RecyclerView、NestedScrollView)将不会继续往上滑动,下面的内容会继续跟着出来,但是同样可以向下滑动隐藏,也可以调用dismiss
和close
关闭。
当这个Dilaog打开再关闭后,无法用Dialog.show()
再次打开,为什么呢?
我去阅读了一下BottomSheetDialog
源代码,发现了如下代码:
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(wrapInBottomSheet(0, view, params));
}
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
final CoordinatorLayout coordinator = View.inflate(getContext(),R.layout...., null);
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
...
return coordinator;
}
private BottomSheetCallback mBottomSheetCallback = new BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
};
也就是说,系统的BottomSheetDialog
是基于BottomSheetBehavior
封装的,这里判断了当我们滑动隐藏了BottomSheetBehavior
中的View后,内部是设置了BottomSheetBehavior
的状态为STATE_HIDDEN
,接着它替我们关闭了Dialog
,所以我们再次调用dialog.show()
的时候Dialog
没法再此打开状态为HIDE的dialog了。
这里就有个疑问了:
Google为啥没有提供我们自己设置BottomSheetCallback
的接口呢?
没有关系,看了源码发现很简单,我们自己来实现,并且在监听到用户滑动关闭BottomSheetDialog
后,我们把BottomSheetBehavior
的状态设置为BottomSheetBehavior.STATE_COLLAPSED
,也就是半个打开状态(BottomSheetBehavior.STATE_EXPANDED
为全打开),根据源码我把设置的方法提供下:
private void setBehaviorCallback() {
View view = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
mBottomSheetDialog.dismiss();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
}
这样就解决了BottomSheetDialog
关闭后不能再次打开的问题了。
源码下载:http://download.csdn.net/detail/yanzhenjie1003/9578770
引用:
一文彻底搞懂 Design 设计的 CoordinatorLayout 和 AppbarLayout 联动,让 Design 设计更简单~
Material Design系列,Behavior之BottomSheetBehavior与BottomSheetDialog
源码:
基本使用