CoordinatorLayout 是 support:design 提供的一个重要布局,虽然由于产品设计的原因,我在正式项目中还没有机会使用到, 不过它提供了一些非常不错的效果,值得记录一下.
Android中处理事件分发还是挺麻烦的,需要从顶层开始,层层分发,拦截,响应.特别是涉及到多层嵌套的时候,需要判断什么时候拦截,拦截后怎么处理;什么时候放行...
CoordinatorLayout 可以用于调度协调子布局,实现联动效果.使用它,可以简化事件的处理.下面是 CoordinatorLayout 的一些常用方式.
CoordinatorLayout 和 FloatingActionButton
FloatingActionButton就是一个按钮,不过可以设置一些5毛特效.
<!--
app:backgroundTint 默认填充色
app:rippleColor 点击时填充色
app:elevation 默认高度(高度越高,阴影越大)
app:pressedTranslationZ 点击时高度
app:layout_anchor 依赖目标
app:layout_anchorGravity 相对依赖目标的位置
-->
Snackbar是从底部弹出的,当它显示的时候,如果遮住了按钮,体验就不太好,CoordinatorLayout 就可以解决这个问题,当使用 CoordinatorLayout 作为容器时,如果显示Snackbar,FloatingActionButton会有一个向上移动的效果
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll"
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:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="com.heihei.hehe.coordinatorlayout.behavior.FloatButtonBehavior"
app:layout_anchor="@id/scroll"
app:layout_anchorGravity="bottom|end"
app:backgroundTint="#fff000"
app:rippleColor="@color/colorAccent"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
关于FloatingActionButton,还可以实现一些更好的特效,不过需要涉及到 CoordinatorLayout 的原理,放在靠后一些的地方.
CoordinatorLayout 和 AppBarLayout
CoordinatorLayout 和 AppBarLayout一起使用,可以实现的效果有很多
Toolbar的快速返回
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll"
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:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
内容上划时,隐藏toolbar,下划时,显示toolbar,实现这个效果的关键在于
- 给滚动视图添加属性
// 只有添加了该属性,才能让CoordinatorLayout 响应子视图的滚动事件
// 注: 滚动视图必须要实现 NestedScrollingChild
app:layout_behavior="@string/appbar_scrolling_view_behavior" - toolbar放在 AppBarLayout 里面(暂时可以认为,只有AppBarLayout 里面的控件,才能响应滑动)
- 个toolbar添加属性
app:layout_scrollFlags="scroll|..."
关于 app:layout_scrollFlags 还可以设置一些其它的值,实现的效果也有区别
scroll
这个值必须有,没有这个值,控件会固定在屏幕上,不响应任何事件
snap
设置这个值后,toolbar不会停止在中间状态,结束状态要么完全显示,要么完全隐藏
enterAlways
向上滚动,隐藏该控件;向下的滚动,显示该控件
enterAlwaysCollapsed
向上滚动,隐藏该控件;
向下滑动:
a.没有设置 minHeight,当滚动视图到达顶部,再显示该控件
b.设置 minHeight,先已最小高度出现,等滚动视图到达顶部,再显示该控件(同时需要设置 enterAlways 才生效)
exitUntilCollapsed
滚动视图向上滚动时,该控件会折叠在顶部,这个在后面再具体说明
toolbar 搭配 tablayout 使用
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"
app:tabIndicatorHeight="3dp"
app:tabTextColor="@color/color_ffffff"
app:tabSelectedTextColor="@color/colorAccent"
app:tabIndicatorColor="@color/colorAccent"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll"
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:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
顺便解释下TabLayout的一些属性
app:tabMode 模式,有两个值:scrollable(可滚动); fixed(固定的)
app:tabIndicatorHeight 指示滑块的高度
app:tabTextColor 文字的颜色
app:tabSelectedTextColor 文字选中状态的颜色
app:tabIndicatorColor 指示滑块的颜色
app:layout_scrollFlags="scroll|enterAlwaysCollapsed|enterAlways"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
exitUntilCollapsed一般结合CollapsingToolbarLayout使用
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="?attr/actionBarSize"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:statusBarScrim="?attr/colorAccent"
app:title="title"
app:collapsedTitleGravity="left"
app:expandedTitleGravity="center_horizontal|bottom"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:src="@mipmap/icon_bg_mine"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
app:layout_collapseMode="pin"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="?attr/homeAsUpIndicator"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll"
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:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
上面的例子中,CollapsingToolbarLayout在滚动视图上滑时,会逐渐收缩折叠在屏幕上方(折叠高度受最小高度影响). 同时 CollapsingToolbarLayout 的子视图可以设置折叠模式
// 折叠模式
app:layout_collapseMode
有两个值:
parallax -> 视差模式,就是上面的图片的变化效果
pin -> 固定模式,在折叠的时候最后固定在顶端
// 视差效果
app:layout_collapseParallaxMultiplier
范围[0.0,1.0],值越大视差越大
CollapsingToolbarLayout 中使用到的几个属性也解释一下:
//折叠后的背景色 -> setContentScrim(Drawable)
app:contentScrim="?attr/colorPrimary"
// 必须设置透明状态栏才有效 -> setStatusBarScrim(Drawable)
app:statusBarScrim="?attr/colorAccent"
// 标题
app:title="title"
// 折叠后的标题位置
app:collapsedTitleGravity="left"
// 打开时的标题位置
app:expandedTitleGravity="center_horizontal|bottom"
PS: AppbarLayout 的展开和关闭是可以通过代码控制的
appbarLayout.setExpanded(true,false);
CoordinatorLayout 和 BottomSheet
通过 CoordinatorLayout 也可以实现底部弹窗的效果,并且效果更好
<android.support.v4.widget.NestedScrollView
android:id="@+id/scroll" android:layout_width="match_parent"
android:layout_height="300dp"
app:behavior_peekHeight="0dp"
app:behavior_hideable="true"
app:layout_behavior="@string/bottom_sheet_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
可以看到,在 CoordinatorLayout 中,只要给某个视图指定属性
app:layout_behavior="@string/bottom_sheet_behavior"
就可以将该视图变为底部菜单的形式,同时可以通过其它几个属性改变菜单的规则
// 关闭时的高度
app:behavior_peekHeight="0dp"
// 是否可以完全隐藏,如果指定为 false,那么将最少已上面的高度显示
app:behavior_hideable="true"
代码中控制
// 初始化
sheetBehavior = BottomSheetBehavior.from(findViewById(R.id.scroll));
// 打开
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
//关闭
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
//隐藏
sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
// 状态监听
sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
// 状态改变时回调
if(newState == BottomSheetBehavior.STATE_EXPANDED){
Toast.makeText(bottomSheet.getContext(),"打开了",Toast.LENGTH_SHORT).show();
}else if(newState == BottomSheetBehavior.STATE_COLLAPSED){
Toast.makeText(bottomSheet.getContext(),"关闭了",Toast.LENGTH_SHORT).show();
}else if(newState == BottomSheetBehavior.STATE_HIDDEN){
Toast.makeText(bottomSheet.getContext(),"隐藏了",Toast.LENGTH_SHORT).show();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
// 拖动时回调
}
});
BottomSheetDialog
BottomSheetDialog 也是一种从底部弹起的对话框,使用起来和 CoordinatorLayout 没什么关系,这里也一起介绍
// 直接通过构造方法初始化
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(v.getContext());
//设置内容
bottomSheetDialog.setContentView(R.layout.content_scrolling);
//显示
bottomSheetDialog.show();
常规的一些用法到这里差不多了. 那么为什么 CoordinatorLayout 可以实现这些效果? 因为在上面我们好像并没有写什么代码,就是简单的谢谢布局,设置一下属性就可以了.
其实CoordinatorLayout自己并不控制View,所有的控制权都在Behavior. Behavior是 CoordinatorLayout 的内部内,是个抽象类. 它的子视图通过实现Behavior,然后CoordinatorLayout就可以进行协同管理.
前面我们使用了AppBarLayout 和 FloatingActionButton ,可以去源码里简单的看一下
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
...
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton {
...
可以看到,这两个控件都使用了Behavior,使用 Behavior的方式也有多种:
- 注解绑定Behavior,当我们使用自定义控件的时候,就可以像上面一样,直接通过注解指定 Behavior.
- 在XML中绑定Behavior
app:layout_behavior="Behavior的包名" - 代码绑定Behavior
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) view.getLayoutParams();
params.setBehavior(behavior);
同时,也可以通过自定义Behavior实现一些特殊的效果
// 如果该Behavior只想给某种控件使用,可以通过泛型控制, 当然也可以不指定,那么任何控件都可以使用
public class MyBehavior<T> extends CoordinatorLayout.Behavior {
// 构造方法必须要写,因为Behavior最终都是通过反射此构造方法初始化
// 可以带有自定义属性
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 视图依赖(想想观察者模式),在这里可以指定具体的对象,也可以指定一个范围
// 比如这里指定了,只观察 AppBarLayout 的变化
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
/**
* 依赖的对象发生了变化(观察者 onNext...),可以在这里做出相应的处理,比如位置改变,大小变化等
* @param child 使用此 Behavior 的控件
* @param dependency 观察的控件
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return true;
}
/**************************以下是滑动事件的相关方法(无需声明依赖,不受依赖影响)*************************/
/**
* (嵌套)滚动事件开始前
* 通过返回值表示要不要响应本次滑动,只有这里返回true,后面的响应方法才会执行
* 比如这里,表示只响应垂直方向的滑动
* @param child 自己
* @param directTargetChild 发起滑动事件的控件
* @param target
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
/**
* (嵌套)滚动事件开始后,滚动视图获得滚动事件前
* @param dy 垂直方向滑动增量
* @param consumed 长度为二 , 水平和垂直方向消耗掉的滚动
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
//dy大于0是向上滚动 小于0是向下滚动
}
/**
* 滚动视图获得(嵌套)滚动事件后
* @param dyConsumed 竖直方向上滑动被消耗了多少
* @param dyUnconsumed 未消耗的
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (dyConsumed > 0 && dyUnconsumed == 0) {
// 上滑
}
if (dyConsumed == 0 && dyUnconsumed > 0) {
// 到边了, 还在上滑
}
if (dyConsumed < 0 && dyUnconsumed == 0) {
// 下滑
}
if (dyConsumed == 0 && dyUnconsumed < 0) {
// 到边了, 还在下滑
}
}
// (嵌套)滚动事件结束后
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
}
// 快速滑动开始前
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) { return false;
}
// 快速滑动
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) { return false;
}
}
自定义Behavior的套路就在上面,注释很详细了,下面是几个具体的例子
1.FloatingActionButton 在向上滚动是隐藏,向下滚动时出现
public class FloatButtonBehavior extends FloatingActionButton.Behavior {
// 构造方法必须有,使用的时候,会通过反射调用该构造方法实例化, 如果没有,会保错
public FloatButtonBehavior(Context context, AttributeSet attrs) {
super();
}
@Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, final View directTargetChild, final View target, final int nestedScrollAxes) {
// Ensure we react to vertical scrolling
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
//上滑隐藏,下滑显示
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}
2.底部菜单的上滑隐藏
public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<View> {
private ObjectAnimator outAnimator,inAnimator;
public BottomNavigationBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 垂直滑动
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
if(dy > 0){//上滑隐藏
if(outAnimator == null){
outAnimator = ObjectAnimator.ofFloat(child,"translationY",0,child.getHeight());
outAnimator.setDuration(200);
}
if(!outAnimator.isRunning() && child.getTranslationY() <= 0){
outAnimator.start();
}
}else if(dy < 0){//下滑显示
if(inAnimator == null){
inAnimator = ObjectAnimator.ofFloat(child,"translationY",child.getHeight(),0);
inAnimator.setDuration(200);
}
if(!inAnimator.isRunning() && child.getTranslationY() >= child.getHeight()){
inAnimator.start();
}
}
}
}
上面的例子用到了 support:design:25.0.0 里的一个新控件 BottomNavigationView
使用方式如下:
<!--
app:itemBackground 按钮背景
app:itemIconTint 图标颜色
app:itemTextColor 文字颜色
app:menu 菜单
-->
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_behavior="com.heihei.hehe.coordinatorlayout.behavior.BottomNavigationBehavior"
app:itemBackground="@color/color_1ec859"
app:itemIconTint="@drawable/tab_text_color_selector"
app:itemTextColor="@drawable/tab_text_color_selector"
app:menu="@menu/menu_navigation"/>
menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/item1"
android:checked="true"
android:icon="@android:drawable/stat_notify_chat"
android:title="Message"/>
<item
android:id="@+id/item2"
android:icon="@android:drawable/stat_notify_error"
android:title="Call"/>
<item
android:id="@+id/item3"
android:icon="@android:drawable/stat_notify_more"
android:title="Contact"/>
<item
android:id="@+id/item4"
android:icon="@android:drawable/stat_notify_sync"
android:title="aaa"/>
</menu>
代码中设置:
navigationView = (BottomNavigationView) findViewById(R.id.navigation);
navigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.item1:
navigationView.setItemBackgroundResource(R.color.color_1ec859);
break;
case R.id.item2:
navigationView.setItemBackgroundResource(R.color.color_3b93eb);
break;
case R.id.item3:
navigationView.setItemBackgroundResource(R.color.color_ffa973);
break;
case R.id.item4:
navigationView.setItemBackgroundResource(R.color.color_ffbc00);
break;
}
Toast.makeText(BottomNavigationActivity.this, item.getTitle(), Toast.LENGTH_SHORT).show();
return false;
}
});
3.头像动画
public class HeaderImageBehavior extends CoordinatorLayout.Behavior {
private float distanceY;
public HeaderImageBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HeadBehavior);
distanceY = a.getDimension(R.styleable.HeadBehavior_openHeight,dip2px(context,250))//750 -a.getDimension(R.styleable.HeadBehavior_closeHeight,dip2px(context,56));//168
a.recycle();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float p = Math.abs(dependency.getY())*1f/distanceY; child.setScaleX(1- p/2);
child.setScaleY(1- p/2);
child.setTranslationX(-child.getLeft()*p);
return true;
}
private int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
CoordinatorLayout 还可以实现更复杂的效果,能力有限,就整理到这里了
网友评论
Demo在这里, 其实代码在文中都贴出来了