Bottom Sheet使用教程
什么是Bottom Sheet?
Bottom Sheet是Design Support Library23.2 版本引入的一個類似于對話框的控件,可以暫且叫做底部彈出框吧。 Bottom Sheet中的內容默認是隱藏起來的,只顯示很小一部分,可以通過在代碼中設置其狀態或者手勢操作將其完全展開,或者完全隱藏,或者部分隱藏。對于Bottom Sheet的描述可以在官網查詢: https://material.io/guidelines/components/bottom-sheets.html#
其實在 Bottom Sheet出現之前已經有人實現了相同的功能,最早的一個可靠版本應該是 AndroidSlidingUpPanel ,當然它實現的原理跟谷歌的方式完全不一樣。
Bottom Sheet的類型
有兩種類型的Bottom Sheet:
1.Persistent bottom sheet:- 通常用于顯示主界面之外的額外信息,它是主界面的一部分,只不過默認被隱藏了,其深度(elevation)跟主界面處于同一級別;還有一個重要特點是在Persistent bottom sheet打開的時候,主界面仍然是可以操作的。ps:Persistent bottom sheet該如何翻譯呢?我覺得翻譯為普通bottom sheet就好了,還看到有人翻譯為“常駐bottom sheet”,可能更接近于英語的字面意思,可是反而不易理解。

2. 模態bottom sheet :- 顧名思義,模態的bottom sheet在打開的時候會阻止和主界面的交互,并且在視覺上會在bottom sheet背后加一層半透明的陰影,使得看上去深度(elevation)更深。
總結起來這兩種Bottom Sheet的區別主要在于視覺和交互上,當然適用方法也是不一樣的。

基本用法
不管是普通bottom sheet還是模態的bottom sheet,都需要依賴:
dependencies {
...
compile 'com.android.support:design:24.1.1'
}
當然現在的app一般都要依賴這個兼容庫,版本號只要保證是在23.2.0及其以后就可以了。
Persistent bottom sheet的用法
其實Persistent bottom sheet不能算是一個控件,因為它只是一個普通的布局在CoordinatorLayout這個布局之下所表現出來的特殊行為。所以其使用方式跟普通的控件也很不一樣,它必須在CoordinatorLayout中,并且是CoordinatorLayout的直接子view。
定義主界面與bottom sheet的布局
為了讓xml代碼看起來不那么長,我們把布局分為content_main和content_bottom_sheet兩部分,content_main主要是一些按鈕,用于切換bottom sheet的狀態,content_bottom_sheet才是bottom sheet的內容。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="
<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>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="
<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>
content_bottom_sheet.xml
這里定義的布局就是bottom sheet的界面。這里是一個相對布局,其實你可以定義任意布局,唯一的要求是需要定義app:layout_behavior="@string/bottom_sheet_behavior",定義了這個屬性就相當于告訴了CoordinatorLayout這個布局是一個bottom sheet,它的顯示和交互都和普通的view不同。@string/bottom_sheet_behavior是一個定義在支持庫中的字符串,等效于android.support.design.widget.BottomSheetBehavior。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="
<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>
其實你還可以看到這里除了app:layout_behavior之外,還有兩個屬性
app:behavior_hideable="true"
app:behavior_peekHeight="60dp"
其中app:behavior_hideable="true"表示你可以讓bottom sheet完全隱藏,默認為false;app:behavior_peekHeight="60dp"表示當為STATE_COLLAPSED(折疊)狀態的時候bottom sheet殘留的高度,默認為0。
當我們按照上面得代碼配置好布局之后,其實一個bottom sheet就已經完成了,在CoordinatorLayout和bottom_sheet_behavior的共同作用下,content_bottom_sheet布局就成了一個bottom sheet, 但是我們還需要知道如何控制它。
控制Persistent bottom sheet
我們在MainActivity.java中添加一些代碼,以處理bottom sheet,以及監聽bottom sheet狀態變化。
bottom sheet有以下5種狀態
-
STATE_COLLAPSED: 默認的折疊狀態, bottom sheets只在底部顯示一部分布局。顯示高度可以通過 app:behavior_peekHeight 設置(默認是0)
-
STATE_DRAGGING : 過渡狀態,此時用戶正在向上或者向下拖動bottom sheet
-
STATE_SETTLING: 視圖從脫離手指自由滑動到最終停下的這一小段時間
-
STATE_EXPANDED: bottom sheet 處于完全展開的狀態:當bottom sheet的高度低于CoordinatorLayout容器時,整個bottom sheet都可見;或者CoordinatorLayout容器已經被bottom sheet填滿。
-
STATE_HIDDEN : 默認無此狀態(可通過app:behavior_hideable 啟用此狀態),啟用后用戶將能通過向下滑動完全隱藏 bottom sheet
bottom sheet的狀態是通過BottomSheetBehavior來設置的,因此需要先得到BottomSheetBehavior對象,然后調用BottomSheetBehavior.setState()來設置狀態,比如設置為折疊狀態:
BottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
我們還可以通過BottomSheetBehavior.getState() 來獲得狀態。
要監聽bottom sheet的狀態變化則使用setBottomSheetCallback方法,之所以需要監聽是因為bottom sheet的狀態還可以通過手勢來改變。
具體使用見下面的代碼:
MainActivity.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>
模態bottom sheet的用法
模態bottom sheet用法跟傳統的dialog很類似,它是一個BottomSheetDialogFragment。
首先定義好BottomSheetDialogFragment的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="
<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>
注意這里不再需要定義behavior 和peekHeight之類的東西了。
創建一個繼承了BottomSheetDialogFragment的CustomBottomSheetDialogFragment 類,在onCreateView方法中把上面的布局傳遞進去
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>
顯示這個模態的bottom sheet
new CustomBottomSheetDialogFragment().show(getSupportFragmentManager(), "Dialog");
與普通bottom sheet不同的是我們不需要處理它的狀態了,因為它跟普通bottom sheet機制都不同,只有打開和關閉狀態,而且是通過點擊bottom sheet之外的區域來取消bottom sheet的。
總結
由此可以看到 Persistent bottom sheet是 最復雜的而 模態bottom sheet 基本沒什么新東西。
在Persistent bottom sheet使用方法小節中我們是點擊一個item切換一個狀態,實際使用肯定不是這樣,一般是點擊一個按鈕,在不同狀態之間toggle。
為此我在上面的基礎上增加一個按鈕,然后在onclick中增加toggle的代碼,順便將BottomSheetDialogFragment的代碼也添加到MainActivity.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>
整個demo的代碼可以在github下載 https://github.com/jianghejie/bottom-sheet-tutorial

補充
Persistent bottom sheet xml布局中的
app:behavior_hideable="true"
app:behavior_peekHeight="60dp"
可以用代碼實現
mBottomSheetBehavior.setHideable(true);
mBottomSheetBehavior.setPeekHeight(300);
第三方的bottom sheet
來自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0327/7729.html