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>