引文
在我的技术群里有小伙伴们讨论Behavior,我也去玩了玩,我也对Behavior写了系列博客。选中Behavior然后ctrol + t后发现Behavior的一个实现类:BottomSheetBehavior,我就到Android官网上翻了下资料,一翻就发现了惊喜啊,下面就把这些惊喜介绍给大家。
更多文章请Google/百度搜索我名字:严振杰,排名第一的就是我。
BottomSheetBehavior怎么玩(知乎Bottom隐藏和显示)
玩这个东西,首先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.Toolbarandroid: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"> <Buttonandroid: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"> <Buttonandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:text="第一" /> <Buttonandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:text="第二" /> <Buttonandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:text="第三" /> <Buttonandroid: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"这个布局的。
public static <V extends View> BottomSheetBehavior<V> 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<V>) behavior;}这个方法会检查这个View是否是CoordinatorLayout的子View,如果是才会去拿到这个View的Behavior,所以诸位也应该明白为什么我开头说Behavior作为CoordinatorLayout子View的LayoutParams了。
private BottomSheetBehavior mBottomSheetBehavior;@Overrideprotected 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));}findViewById(R.id.btn_bottom_sheet_control).setOnClickListener(onClickListener);是给刚才说的页面中的Button设置了监听,我们用这个按钮来控制tab_layout的显示和隐藏。
/** * The bottom sheet is dragging. */public static final int STATE_DRAGGING = 1;/** * The bottom sheet is settling. */public static final int STATE_SETTLING = 2;/** * The bottom sheet is expanded. */public static final int STATE_EXPANDED = 3;/** * The bottom sheet is collapsed. */public static final int STATE_COLLAPSED = 4;/** * The bottom sheet is hidden. */public static final int STATE_HIDDEN = 5;当我看到STATE_EXPANDED和STATE_COLLAPSED就明白了它的用法了,不就是展开和隐藏起来了麽?所以我们判断这个状态,如果是隐藏就显示,如果是显示就隐藏:
@Overridepublic void onClick(View v) { if (v.getId() == R.id.btn_bottom_sheet_control) { if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); } else if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); } }}到这里,知乎首页的Bottom的隐藏和显示也就讲玩了,接下来我们来看看支付宝淘宝的下方弹窗如何实现。
private BottomSheetDialog mBottomSheetDialog;@Overrideprotected 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:
<?xml version="1.0" encoding="utf-8"?><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" />然后用下面的代码去控制它的显示和隐藏。
if (mBottomSheetDialog.isShowing()) { mBottomSheetDialog.dismiss();} else { mBottomSheetDialog.show();}当这个Dialog Show出来的时候发现它显示了一半,嗯这个效果确实不错,这样就达到了我们最初说的支付宝密码弹窗和淘宝/天猫商品属性选择。我们滑动的时候如果下面有内容它就会EXPANDED,如果是一个普通的View(非RecyclerView、NestedScrollView)将不会继续往上滑动,下面的内容会继续跟着出来,但是同样可以向下滑动隐藏,也可以调用dismiss和close关闭。
@Overridepublic 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了。
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关闭后不能再次打开的问题了。