ある時期からスマホのUIに、画面下から「ニュッ」とせり出してくるメニューともダイアログともとれる動作が見られるようになった。このようなUIは、おそらくスマホの大画面化に伴い、片手で操作しやすいように、下の方に操作系を配置するためのものなのだと思う。
私は長らくこのUIを自分でも使いたいと思い、どうやってやるのだろうと調べていた。おそらくカスタムしたダイアログかカスタムFragmentであろうと「下から出てくる ダイアログ」といったワードで検索してみたが、そういったものを解説しているウェブページは見つからなかった。
しかし、こちらのページがきっかけで求めているものがBottomSheetという仕組みだとわかった。
BottomSheetはマテリアルデザインにおけるコンポーネントのひとつで「スクリーンの下からスライドアップして追加のコンテンツを表示する要素」 と定義されているらしい。ダイアログやメニューといった独立したオブジェクトだと思っていたけど、実は画面レイアウト上の仕組みだったのである。
そんなわけで、ここではBottomSheetの基本的な使い方を記しておく。
Gradle
Gradleに以下の記述を追加する。
後ろのバージョン番号はこの記事を書いている時点のものなので、最新のバージョンを指定すること。
dependencies { … implementation 'com.google.android.material:material:1.3.0' …. }
レイアウト
BottomSheetとして扱うViewは必ずCoordinatorLayoutの直下に置かなければならない。そのうえで、そのViewがBottomSheetとしてふるまうことを属性で指定する。
app:layout_behavior
CoordinatorLayout直下のView(LinearLayout等)のlayout_behavior属性に 「android.support.design.widget.BottomSheetBehavior」を指定する。これにはシステムのstringリソースで「@string/bottom_sheet_behavior」という別名が付けられている。
app:behavior_peekHeight
縮小時の高さをbehavior_peekHeight属性で指定する。
app:behavior_hideable
下までスワイプした時にBottomSheetを非表示にするかどうかをbehavior_hideable属性で指定する。
<androidx.coordinatorlayout.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">
<!-- CoordinatorLayout直下のViewに -->
<!-- app:layout_behavior="@string/bottom_sheet_behavior"を設定 -->
<LinearLayout
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_peekHeight="50dp"
app:behavior_hideable="false"
app:layout_behavior="@string/bottom_sheet_behavior">
BottomSheetの操作
BottomSheetBhaviorの取得
レイアウトXMLで指定したBottmSheetの状態を取得したり、プログラムから操作するにはBottomSheetBehaviorのインスタンスを取得する
//Viewのインスタンス
LinearLayout bottomSheet = (LinearLayout)findViewById(R.id.bottom_sheet);
//BottomSheetBhaviorのインスタンス
BottomSheetBehavior<LinearLayout> behavior = BottomSheetBehavior.from(bottomSheet);
BottomSheetの状態
BottomSheetの状態はBottomSheetBehaviorのgetState()メソッドで取得することができる。
メソッドの戻り値は以下の通り
値 | 定数 | 説明 |
---|---|---|
STATE_DRAGGING | 1 | ボトムシートをつかんで移動開始 |
STATE_SETTLING | 2 | STATE_EXPANDED、STATE_COLLAPSEDへの遷移中 |
STATE_EXPANDED | 3 | 全表示中 |
STATE_COLLAPSED | 4 | 縮小化(peekHeightで指定した高さ)で表示中 |
STATE_HIDDEN | 5 | 非表示中(画面下に隠れた状態) |
BottomSheetの状態操作
BottomSheetの状態を変化させるにはBottomSheetBehaviorのsetState()メソッドで状態をセットする。
//BottomSheetの状態を展開状態にする
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
BottomSheetの状態変化
BottomSheetBehaviorのaddBottomSheetCallbackで、BottomSheetが「全表示になった」「縮小化された」等、状態の変化を捉えることができる。
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
//状態変化した後の処理
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
//スライド中の処理
}
});
サンプルアプリ
以下にBottomSheetを使った簡単なサンプルを示す。
レイアウトファイル
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="50sp"
android:gravity="center_horizontal"/>
<LinearLayout
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/black"
app:behavior_peekHeight="50dp"
app:behavior_hideable="false"
app:layout_behavior="@string/bottom_sheet_behavior">
<Button
android:id="@+id/bottomsheet_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@color/black"
android:text="↑"
android:textStyle="bold"
android:textSize="20sp"/>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:foreground="@android:color/darker_gray"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="80sp"
android:text="Goodbye World"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
JAVAファイル
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout bottomSheet = (LinearLayout)findViewById(R.id.bottom_sheet);
final Button bsSwitch = (Button)findViewById(R.id.bottomsheet_switch);
final BottomSheetBehavior<LinearLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
//状態が変化したらボタンのテキストを変える
switch (newState) {
case BottomSheetBehavior.STATE_COLLAPSED:
bsSwitch.setText("↑");
break;
case BottomSheetBehavior.STATE_EXPANDED:
bsSwitch.setText("↓");
break;
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
bsSwitch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//ボタンを押したときの動作
switch (behavior.getState()) {
case BottomSheetBehavior.STATE_EXPANDED:
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
break;
case BottomSheetBehavior.STATE_COLLAPSED:
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
break;
default:
break;
}
}
});
}
}
このアプリを起動すると下のような画面が表示される。
下の黒い帯を上にスワイプするとBottomSheetが展開され「GoodBye World」とテキストが表示され、ここから下にスワイプすると縮小状態に戻る。「↑」はボタンで、これをタップしてもBottomSheet展開され、再度ボタンをタップすると縮小状態に戻る。