Bottom Sheet使用教程
ahdubi
8年前
<h2>什么是Bottom Sheet?</h2> <p>Bottom Sheet是Design Support Library23.2 版本引入的一个类似于对话框的控件,可以暂且叫做底部弹出框吧。 Bottom Sheet中的内容默认是隐藏起来的,只显示很小一部分,可以通过在代码中设置其状态或者手势操作将其完全展开,或者完全隐藏,或者部分隐藏。对于Bottom Sheet的描述可以在官网查询: <a href="/misc/goto?guid=4959746261963616175" rel="nofollow,noindex">https://material.io/guidelines/components/bottom-sheets.html#</a></p> <p>其实在 Bottom Sheet出现之前已经有人实现了相同的功能,最早的一个可靠版本应该是 <a href="/misc/goto?guid=4958988893246087416" rel="nofollow,noindex">AndroidSlidingUpPanel</a> ,当然它实现的原理跟谷歌的方式完全不一样。</p> <h2>Bottom Sheet的类型</h2> <p>有两种类型的Bottom Sheet:</p> <p>1.Persistent bottom sheet:- 通常用于显示主界面之外的额外信息,它是主界面的一部分,只不过默认被隐藏了,其深度(elevation)跟主界面处于同一级别;还有一个重要特点是在Persistent bottom sheet打开的时候,主界面仍然是可以操作的。ps:Persistent bottom sheet该如何翻译呢?我觉得翻译为普通bottom sheet就好了,还看到有人翻译为“常驻bottom sheet”,可能更接近于英语的字面意思,可是反而不易理解。</p> <p><img src="https://simg.open-open.com/show/0fbcd1c44e9369e959677200d6172603.png"></p> <p>2. <strong>模态bottom sheet</strong> :- 顾名思义,模态的bottom sheet在打开的时候会阻止和主界面的交互,并且在视觉上会在bottom sheet背后加一层半透明的阴影,使得看上去深度(elevation)更深。</p> <p>总结起来这两种Bottom Sheet的区别主要在于视觉和交互上,当然适用方法也是不一样的。</p> <p><img src="https://simg.open-open.com/show/72e88dcb9cb3c4a09fa5a26c68945ff4.png"></p> <h2>基本用法</h2> <p>不管是普通bottom sheet还是模态的bottom sheet,都需要依赖:</p> <pre> <code class="language-xml">dependencies { ... compile 'com.android.support:design:24.1.1' }</code></pre> <p>当然现在的app一般都要依赖这个兼容库,版本号只要保证是在23.2.0及其以后就可以了。</p> <h3>Persistent bottom sheet的用法</h3> <p>其实Persistent bottom sheet不能算是一个控件,因为它只是一个普通的布局在CoordinatorLayout这个布局之下所表现出来的特殊行为。所以其使用方式跟普通的控件也很不一样,它必须在CoordinatorLayout中,并且是CoordinatorLayout的直接子view。</p> <p>定义主界面与bottom sheet的布局</p> <p>为了让xml代码看起来不那么长,我们把布局分为content_main和content_bottom_sheet两部分,content_main主要是一些按钮,用于切换bottom sheet的状态,content_bottom_sheet才是bottom sheet的内容。</p> <pre> <code class="language-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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.androidtutorialshub.bottomsheets.MainActivity"> <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" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <!-- Main Content --> <include layout="@layout/content_main" /> <!-- Bottom Sheet Content --> <include layout="@layout/content_bottom_sheet" /> </android.support.design.widget.CoordinatorLayout></code></pre> <p>content_main.xml</p> <pre> <code class="language-xml"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.androidtutorialshub.bottomsheets.MainActivity" tools:showIn="@layout/activity_main"> <Button android:id="@+id/expand_bottom_sheet_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/text_expand_bottom_sheet" /> <Button android:id="@+id/collapse_bottom_sheet_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/expand_bottom_sheet_button" android:text="@string/text_collapse_bottom_sheet" /> <Button android:id="@+id/hide_bottom_sheet_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/collapse_bottom_sheet_button" android:text="@string/text_hide_bottom_sheet" /> <Button android:id="@+id/show_bottom_sheet_dialog_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/hide_bottom_sheet_button" android:text="@string/text_show_bottom_sheet_dialog" /> </RelativeLayout></code></pre> <p>content_bottom_sheet.xml</p> <p>这里定义的布局就是bottom sheet的界面。这里是一个相对布局,其实你可以定义任意布局,唯一的要求是需要定义app:layout_behavior="@string/bottom_sheet_behavior",定义了这个属性就相当于告诉了CoordinatorLayout这个布局是一个bottom sheet,它的显示和交互都和普通的view不同。@string/bottom_sheet_behavior是一个定义在支持库中的字符串,等效于android.support.design.widget.BottomSheetBehavior。</p> <pre> <code class="language-xml"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/bottomSheetLayout" android:layout_width="match_parent" android:layout_height="300dp" android:background="@android:color/holo_orange_light" android:padding="@dimen/activity_vertical_margin" app:behavior_hideable="true" app:behavior_peekHeight="60dp" app:layout_behavior="@string/bottom_sheet_behavior"> <TextView android:id="@+id/bottomSheetHeading" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/text_expand_me" android:textAppearance="@android:style/TextAppearance.Large" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/bottomSheetHeading" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/activity_horizontal_margin" android:text="@string/text_welcome_message" android:textAppearance="@android:style/TextAppearance.Large" /> </RelativeLayout></code></pre> <p>其实你还可以看到这里除了app:layout_behavior之外,还有两个属性</p> <pre> <code class="language-xml"> app:behavior_hideable="true" app:behavior_peekHeight="60dp"</code></pre> <p>其中app:behavior_hideable="true"表示你可以让bottom sheet完全隐藏,默认为false;app:behavior_peekHeight="60dp"表示当为STATE_COLLAPSED(折叠)状态的时候bottom sheet残留的高度,默认为0。</p> <p>当我们按照上面得代码配置好布局之后,其实一个bottom sheet就已经完成了,在CoordinatorLayout和bottom_sheet_behavior的共同作用下,content_bottom_sheet布局就成了一个bottom sheet, 但是我们还需要知道如何控制它。</p> <p>控制Persistent bottom sheet</p> <p>我们在MainActivity.java中添加一些代码,以处理bottom sheet,以及监听bottom sheet状态变化。</p> <p>bottom sheet有以下5种状态</p> <ul> <li> <p>STATE_COLLAPSED: 默认的折叠状态, bottom sheets只在底部显示一部分布局。显示高度可以通过 app:behavior_peekHeight 设置(默认是0)</p> </li> <li> <p>STATE_DRAGGING : 过渡状态,此时用户正在向上或者向下拖动bottom sheet</p> </li> <li> <p>STATE_SETTLING: 视图从脱离手指自由滑动到最终停下的这一小段时间</p> </li> <li> <p>STATE_EXPANDED: bottom sheet 处于完全展开的状态:当bottom sheet的高度低于CoordinatorLayout容器时,整个bottom sheet都可见;或者CoordinatorLayout容器已经被bottom sheet填满。</p> </li> <li> <p>STATE_HIDDEN : 默认无此状态(可通过app:behavior_hideable 启用此状态),启用后用户将能通过向下滑动完全隐藏 bottom sheet</p> </li> </ul> <p>bottom sheet的状态是通过BottomSheetBehavior来设置的,因此需要先得到BottomSheetBehavior对象,然后调用BottomSheetBehavior.setState()来设置状态,比如设置为折叠状态:</p> <pre> <code class="language-xml">BottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);</code></pre> <p>我们还可以通过BottomSheetBehavior.getState() 来获得状态。</p> <p>要监听bottom sheet的状态变化则使用setBottomSheetCallback方法,之所以需要监听是因为bottom sheet的状态还可以通过手势来改变。</p> <p>具体使用见下面的代码:</p> <p>MainActivity.java</p> <pre> <code class="language-java">package com.androidtutorialshub.bottomsheets; import android.os.Bundle; import android.support.design.widget.BottomSheetBehavior; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener { // BottomSheetBehavior variable private BottomSheetBehavior bottomSheetBehavior; // TextView variable private TextView bottomSheetHeading; // Button variables private Button expandBottomSheetButton; private Button collapseBottomSheetButton; private Button hideBottomSheetButton; private Button showBottomSheetDialogButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initListeners(); } /** * method to initialize the views */ private void initViews() { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.bottomSheetLayout)); bottomSheetHeading = (TextView) findViewById(R.id.bottomSheetHeading); expandBottomSheetButton = (Button) findViewById(R.id.expand_bottom_sheet_button); collapseBottomSheetButton = (Button) findViewById(R.id.collapse_bottom_sheet_button); hideBottomSheetButton = (Button) findViewById(R.id.hide_bottom_sheet_button); showBottomSheetDialogButton = (Button) findViewById(R.id.show_bottom_sheet_dialog_button); } /** * method to initialize the listeners */ private void initListeners() { // register the listener for button click expandBottomSheetButton.setOnClickListener(this); collapseBottomSheetButton.setOnClickListener(this); hideBottomSheetButton.setOnClickListener(this); showBottomSheetDialogButton.setOnClickListener(this); // Capturing the callbacks for bottom sheet bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(View bottomSheet, int newState) { if (newState == BottomSheetBehavior.STATE_EXPANDED) { bottomSheetHeading.setText(getString(R.string.text_collapse_me)); } else { bottomSheetHeading.setText(getString(R.string.text_expand_me)); } // Check Logs to see how bottom sheets behaves switch (newState) { case BottomSheetBehavior.STATE_COLLAPSED: Log.e("Bottom Sheet Behaviour", "STATE_COLLAPSED"); break; case BottomSheetBehavior.STATE_DRAGGING: Log.e("Bottom Sheet Behaviour", "STATE_DRAGGING"); break; case BottomSheetBehavior.STATE_EXPANDED: Log.e("Bottom Sheet Behaviour", "STATE_EXPANDED"); break; case BottomSheetBehavior.STATE_HIDDEN: Log.e("Bottom Sheet Behaviour", "STATE_HIDDEN"); break; case BottomSheetBehavior.STATE_SETTLING: Log.e("Bottom Sheet Behaviour", "STATE_SETTLING"); break; } } @Override public void onSlide(View bottomSheet, float slideOffset) { } }); } /** * onClick Listener to capture button click * * @param v */ @Override public void onClick(View v) { switch (v.getId()) { case R.id.collapse_bottom_sheet_button: // Collapsing the bottom sheet bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); break; case R.id.expand_bottom_sheet_button: // Expanding the bottom sheet bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); break; case R.id.hide_bottom_sheet_button: // Hiding the bottom sheet bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); break; case R.id.show_bottom_sheet_dialog_button: break; } } }</code></pre> <h3>模态bottom sheet的用法</h3> <p>模态bottom sheet用法跟传统的dialog很类似,它是一个BottomSheetDialogFragment。</p> <p>首先定义好BottomSheetDialogFragment的布局:</p> <pre> <code class="language-xml"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/bottomSheetLayout" android:layout_width="match_parent" android:layout_height="300dp" android:background="@android:color/holo_red_light" android:padding="@dimen/activity_vertical_margin" > <TextView android:id="@+id/bottomSheetHeading" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/text_dialog_bottom_sheet" android:textAppearance="@android:style/TextAppearance.Large" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/bottomSheetHeading" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/activity_horizontal_margin" android:text="@string/text_welcome_message" android:textAppearance="@android:style/TextAppearance.Large" /> </RelativeLayout></code></pre> <p>注意这里不再需要定义behavior 和peekHeight之类的东西了。</p> <p>创建一个继承了BottomSheetDialogFragment的CustomBottomSheetDialogFragment 类,在onCreateView方法中把上面的布局传递进去</p> <pre> <code class="language-java">package com.androidtutorialshub.bottomsheets; import android.os.Bundle; import android.support.design.widget.BottomSheetDialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class CustomBottomSheetDialogFragment extends BottomSheetDialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.content_dialog_bottom_sheet, container, false); return v; } }</code></pre> <p>显示这个模态的bottom sheet</p> <pre> <code class="language-xml"> new CustomBottomSheetDialogFragment().show(getSupportFragmentManager(), "Dialog");</code></pre> <p>与普通bottom sheet不同的是我们不需要处理它的状态了,因为它跟普通bottom sheet机制都不同,只有打开和关闭状态,而且是通过点击bottom sheet之外的区域来取消bottom sheet的。</p> <h2>总结</h2> <p>由此可以看到 <strong>Persistent bottom sheet是</strong> 最复杂的而 <strong>模态bottom sheet</strong> 基本没什么新东西。</p> <p>在Persistent bottom sheet使用方法小节中我们是点击一个item切换一个状态,实际使用肯定不是这样,一般是点击一个按钮,在不同状态之间toggle。</p> <p>为此我在上面的基础上增加一个按钮,然后在onclick中增加toggle的代码,顺便将BottomSheetDialogFragment的代码也添加到MainActivity.java中:</p> <pre> <code class="language-java">@Override public void onClick(View v) { switch (v.getId()) { case R.id.collapse_bottom_sheet_button: // Collapsing the bottom sheet bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); break; case R.id.expand_bottom_sheet_button: // Expanding the bottom sheet bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); break; case R.id.hide_bottom_sheet_button: // Hiding the bottom sheet bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); break; case R.id.show_bottom_sheet_dialog_button: // Opening the Dialog Bottom Sheet new CustomBottomSheetDialogFragment().show(getSupportFragmentManager(), "Dialog"); break; case R.id.bottom_sheet_toggle: if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED ){ bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } else if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN || bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED){ bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); } break; } }</code></pre> <p>整个demo的代码可以在github下载 <a href="/misc/goto?guid=4959746262074648741" rel="nofollow,noindex">https://github.com/jianghejie/bottom-sheet-tutorial</a></p> <p><img src="https://simg.open-open.com/show/9ef99c3123e4a6d8146bd947132e2c6e.gif"></p> <h2>补充</h2> <p>Persistent bottom sheet xml布局中的</p> <pre> <code class="language-xml"> app:behavior_hideable="true" app:behavior_peekHeight="60dp"</code></pre> <p>可以用代码实现</p> <pre> <code class="language-xml">mBottomSheetBehavior.setHideable(true); mBottomSheetBehavior.setPeekHeight(300);</code></pre> <h2>第三方的bottom sheet</h2> <ul> <li> <p><a href="/misc/goto?guid=4958988893246087416" rel="nofollow,noindex">AndroidSlidingUpPanel</a></p> </li> <li> <p><a href="/misc/goto?guid=4958876443411678497" rel="nofollow,noindex">Flipboard/bottomsheet</a></p> </li> <li> <p><a href="/misc/goto?guid=4959746262206146128" rel="nofollow,noindex">ThreePhasesBottomSheet</a></p> </li> <li> <p><a href="/misc/goto?guid=4959746262286706064" rel="nofollow,noindex">Foursquare BottomSheet Tutorial</a></p> </li> </ul> <p> </p> <p>来自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0327/7729.html</p> <p> </p>