终于写到 Behavior 这个东东了,这个东西是随着 5.0 系统,是 CoordinatorLayout 的一个重要特性,重要到现在很多 app 都在用。
什么是 Behavior
上面说到 Behavior 是 CoordinatorLayout 的一个重要特性,我们可以管他叫:联动。啥,为啥叫这个名字,因为 Behavior 是把页面中可滚动控件的滚动事件和其他任何对这个滚动事件有兴趣的控件结合起来。在封装上看就是把滚动事件抛出来,由感兴趣的控件来消费,很符合观察者模式的思想。
大家试想,之前我们监听滚动事件都是在滚动控件上注册 listener 对象,代码上适合对象强耦合的,现在呢,google 把这种行为抽象成 Behavior 这个接口,提供一些列默认实现,也可以去自定义,当然自定义才是最重要的环节,并且包含部分 view 的布局方法在内,让我们有很大的定制行,最主要的是可以做到代码的解耦,功能上的代码分割,更利于我们维护。
Behavior 的系统默认实现
google 提供了很多的 Behavior 实现,比如AppbarLayout内部的Behavior,专门协调 AppbarLayout 与可滚动View(NestedScrollView,RecyclerView )的, FloatActionButton内部的Behavior ,协调和Snackbar 的关系,保证Snackbar 弹出的时候不被FAB 遮挡。还有就是上面说的Snackbar内部的Behavior 等等。
另外还提供了几个封装好的带很不错效果的 Behavior 实现:
- BottomSheetBehavior
- BottomSheetDialog
- SwipeDissmissBehavior
这2个 Behavior 的实现就是这节我们学习的内容,学习总是先易后难,自定义 Behavior 下一节说。
Behavior 的使用注意项
Behavior 是 CoordinatorLayout 中 LayoutParams 的一个属性,我们知道子类的 LayoutParams 类型是父类的 LayoutParams 类型,那么子类的子类的 LayoutParams 类型可就不是父类的 LayoutParams 类型,所以注意一下:
- CoordinatorLayout 必须作为项目的跟节点
- 想使用 Behavior 的 view 必须是 CoordinatorLayout 的直接子类
BottomSheetBehavior
什么是 BottomSheetBehavior ,从效果来描述就是底部弹出的卡片,看下效果图:
ps: 有个简单的例子介绍的比我清楚
看着是不是有些熟悉啊,一些 app 的点击分享弹出的 view 不就是这个样子的嘛!
使用起来也是很简单的:
- 我们在 xml 中给目标 view 添加这个 app:layout_behavior="@string/bottom_sheet_behavior"
- 我们使用代码获取到这个 BottomSheetBehavior 对象, BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(shareView)
- 我们使用 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED) 设置状态的方法就能控制展开还是折叠
我们主要关心3个参数
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior"
// peekHeight 是当 Bottom Sheets 关闭的时候,底部我们能看到的高度,默认是0不可见。
app:behavior_peekHeight="50dp"
// hideable是当我们拖拽下拉的时候,bottom sheet是否能全部隐藏。
app:behavior_hideable="true"
需要注意的是:
- BottomSheetBehavior 默认是显示折叠状态的
- app:behavior_peekHeight="0dp" 属性设置 view 折叠状态的高度
- 设置 behavior_peekHeight=0 后才能做到默认不显示底部卡片,或者用代码设置状态也可以做到默认不显示、
setBottomSheetCallback 可以监听回调状态, onStateChanged 监听状态的改变, onSlide 是拖拽的回调, onStateChanged 可以监听到的回调一共有5种:
BottomSheetBehavior 的5种状态:
- STATE_EXPANDED
展开状态,显示完整布局。 - STATE_COLLAPSED
折叠状态,显示peekHeigth 的高度,如果peekHeight为0,则全部隐藏,与STATE_HIDDEN效果一样。 - STATE_DRAGGING
拖拽时的状态 - STATE_HIDDEN
隐藏时的状态 - STATE_SETTLING
释放时的状态
xml
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="switchState"
android:text="展开显示"
android:textSize="22sp"/>
<android.support.constraint.ConstraintLayout
android:id="@+id/view_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior">
<TextView
android:id="@+id/view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="switchState"
android:text="第一行代码"
android:textSize="22sp"/>
<TextView
android:id="@+id/view2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:onClick="switchState"
android:text="第二行代码"
android:textSize="22sp"
app:layout_constraintTop_toBottomOf="@id/view1"/>
<TextView
android:id="@+id/view3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:onClick="switchState"
android:text="开发艺术探索"
android:textSize="22sp"
app:layout_constraintTop_toBottomOf="@id/view2"/>
<TextView
android:id="@+id/view4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:onClick="switchState"
android:text="android进阶之光"
android:textSize="22sp"
app:layout_constraintTop_toBottomOf="@id/view3"/>
</android.support.constraint.ConstraintLayout>
</android.support.design.widget.CoordinatorLayout>
java
public class BottomSheetBehaviorActivity extends AppCompatActivity {
private View view_bottom;
private BottomSheetBehavior mBottomSheetBehavior;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom_sheet_behavior);
view_bottom = findViewById(R.id.view_bottom);
mBottomSheetBehavior = BottomSheetBehavior.from(view_bottom);
}
public void switchState(View view) {
if (!(view instanceof Button) || mBottomSheetBehavior == null) {
return;
}
Button button = (Button) view;
if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
button.setText("展开显示");
return;
}
if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
button.setText("折叠收起");
return;
}
}
BottomSheetDialog
BottomSheetDialog 是对 BottomSheetBehavior 的封装,用一个 dialog 作为模板,绑定我们的特定 layout 布局,BottomSheetDialog 的效果比 BottomSheetBehavior 要好玩多了,他可以支持拖拽到顶部,成为一个 activity 的样子,使用上和 dialog 是一样的。
大家注意啊,BottomSheetDialog 是系统实现,不是自定义实现啊,我刚看的时候误认为是自定义实现了,还去看源码呢。。。
先看看效果:
if (dialog == null) {
dialog = new BottomSheetDialog(this);
dialog.setContentView(R.layout.layout_book_list2);
dialog.setCanceledOnTouchOutside(true);
dialog.setCancelable(true);
}
dialog.show();
是不是很简单,就是这么任性,就是一个 dialog...
网易云音乐的播放列表,高德地图的路线规划都是用这个做的,都是可以拖拽到顶作为一个 activity 的样子去显示,然后滑动关闭,说实话这个效果第一次使用真是惊艳我了,还在想这个是怎么实现的呢,肯定很复杂吧,要让 view 展示出来,然后监听滑动,拖拽到顶停止,然后监听滑动做位移,隐藏 view,好复杂。没想到原来就是这么一个 dialog,真是想感叹封装的强大啊,所以学号设计模式,然后做好封装才是代码功底的极大提现啊。
SwipeDissmissBehavior
这个效果真心觉得没啥用,效果号僵硬好烂,就是view 随着手指的左移右移,然后位移隐藏自己,隐藏不能随着手机移动,很僵硬。
老规矩还是来看下:
还是给我们的目标 view 添加这个 Behavior 即可,然后代码 new 一个 SwipeDismissBehavior 出来,设置参数,然后绑定给 view 的 layoutParams 参数
mSwipeLayout = findViewById(R.id.swipe_layout);
SwipeDismissBehavior swipe = new SwipeDismissBehavior();
/**
* //设置滑动的方向,有3个值
*
* 1,SWIPE_DIRECTION_ANY 表示向左像右滑动都可以,
* 2,SWIPE_DIRECTION_START_TO_END,只能从左向右滑
* 3,SWIPE_DIRECTION_END_TO_START,只能从右向左滑
*/
swipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
swipe.setStartAlphaSwipeDistance(0f);
swipe.setSensitivity(0.2f);
swipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
Log.e(TAG,"------>onDissmiss");
}
@Override
public void onDragStateChanged(int state) {
Log.e(TAG,"------>onDragStateChanged");
}
});
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mSwipeLayout.getLayoutParams();
if(layoutParams!=null){
layoutParams.setBehavior(swipe);
}
ps: demo 地址: github