LoginSignup
5
2

More than 5 years have passed since last update.

BottomSheetが上から降ってくる?と思ったときにチェックすること

Last updated at Posted at 2018-02-20

自分用の調査メモです。

どういうコードで問題が発生するか

何かのAPIのロードが終わってからボトムシートを表示したい

Slice.png

と思ってこんなコードを書いてた

擬似コード
    private fun showOrHideBottomSheet(show: Boolean) {
        if (show) {
            bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
            bottomSheet.isHideable = false

        } else {
            bottomSheet.isHideable = true
            bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
        }
    }
擬似コンテンツ
<?xml version="1.0" encoding="utf-8"?>
<layout>
    <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.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <LinearLayout
            android:id="@+id/bottom_sheet"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:minHeight="48dp"
            android:background="#0cf"
            app:layout_behavior="@string/bottom_sheet_behavior">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:gravity="center"
                android:textColor="@android:color/white"
                android:text="BottomSheetだ"/>
            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="240dp"
                android:layout_margin="8dp"
                android:background="#fc0">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="でっかいコンテンツ"/>
            </FrameLayout>
        </LinearLayout>
    </android.support.design.widget.CoordinatorLayout>
</layout>

見よ!ボトムシートが上から降ってくるぞい!

test.gif

対処1:ボトムシート対象のビューに layout_gravity="bottom" 指定してみる

        <LinearLayout
            android:id="@+id/bottom_sheet"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"   ★★★ ← 追加 ★★★
            android:orientation="vertical"
            android:minHeight="48dp"
            android:background="#0cf"
            app:layout_behavior="@string/bottom_sheet_behavior">

test.gif

ビュー全体が上から降ってくる、という現象は起きなくなった。
しかしLinearLayoutの内包してるコンテンツ分の高さ分が見えてから引っ込むような挙動になっている。

対処2:isHideable=falseを一旦取ってみる

    private fun showOrHideBottomSheet(show: Boolean) {
        if (show) {
            bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
            //bottomSheet.isHideable = false  ★★★ ← コメントアウト ★★★

        } else {
            bottomSheet.isHideable = true
            bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
        }
    }

お????? なおった???

test.gif

hiddenにできてしまう、という問題はありつつ、上から降ってこなくなった!

対処3:hideable=falseを指定するタイミングを、ボトムシートがHIDDENじゃなくなってからにする

   bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
   Log.d("MainActivity", "showOrHideBottomSheet: ここでhideableにしたいけどできなさそう")
   bottomSheet.isHideable = false

ってログを仕掛けてみるとわかるのだけど

02-20 00:37:02.657 D/MainActivity(10416): showOrHideBottomSheet: ここでhideableにしたいけどできなさそう
02-20 00:37:02.680 D/MainActivity(10416): BottomSheetCallback#onStateChanged: newState=2
02-20 00:37:03.090 D/MainActivity(10416): BottomSheetCallback#onStateChanged: newState=4

実は STATE_COLLAPSEDをセットしてから実際に状態が変わるまではちょっと時間がかかる
なので、hiddenにするタイミングをずらしてやると対処はできそう。

擬似コード(修正版)
    private fun setup() {
        //...

        bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(view: View, newState: Int) {
                Log.d(LOG_TAG, "BottomSheetCallback#onStateChanged: newState=${newState}")

                // hideable=falseに指定するのは、実際にHIDDENじゃなくなってから
                if (newState != BottomSheetBehavior.STATE_HIDDEN && !bottomSheetHideable && bottomSheet.isHideable) {
                    Log.d(LOG_TAG, "showOrHideBottomSheet: ここでhideableにする")
                    bottomSheet.isHideable = false
                }
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {

            }
        })
    }

    private fun showOrHideBottomSheet(show: Boolean) {
        if (show) {
            bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
            Log.d(LOG_TAG, "showOrHideBottomSheet: ここでhideableにしたいけどできなさそう")
            bottomSheetHideable = false

        } else {
            bottomSheetHideable = true
            bottomSheet.isHideable = true
            bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
        }
    }

ログ的にはこんな感じになって、

02-20 01:14:34.048 D/MainActivity(11664): showOrHideBottomSheet: ここでhideableにしたいけどできなさそう
02-20 01:14:34.061 D/MainActivity(11664): BottomSheetCallback#onStateChanged: newState=2
02-20 01:14:34.061 D/MainActivity(11664): showOrHideBottomSheet: ここでhideableにする
02-20 01:14:34.474 D/MainActivity(11664): BottomSheetCallback#onStateChanged: newState=4

実動作を見ると...

test.gif

キタ━━━━(゚∀゚)━━━━!! キタ━━━━(゚∀゚)━━━━!! キタ━━━━(゚∀゚)━━━━!!

ってなります。

 

後始末:少しだけまともなコードにする

擬似コードだとHideableフラグをActivityに直接もたせてるけど、実際のプロダクションコードだと、ActivityとかFragmentにボトムシートのHideableかどうかフラグなんて持たせたくない。

ということで、

BottomSheetHideableHelper.kt
internal class BottomSheetHideableHelper(private val bottomSheet: BottomSheetBehavior<*>) {
    private var hideable: Boolean = false

    fun setHideable(hideable: Boolean) {
        this.hideable = hideable

        if (!hideable) {
            // stateがhiddenじゃなければ、即座にhideable=falseにする
            if (bottomSheet.state != BottomSheetBehavior.STATE_HIDDEN && bottomSheet.isHideable) {
                bottomSheet.isHideable = false
            }
        } else {
            // hideable=trueはいつでもOK
            bottomSheet.isHideable = true
        }
    }

    fun onStateChanged(newState: Int) {
        // state がhiddenじゃなくなったのを待ち合わせてからhideable=falseにする
        if (newState != BottomSheetBehavior.STATE_HIDDEN && !hideable && bottomSheet.isHideable) {
            bottomSheet.isHideable = false
        }
    }
}

こんな感じでヘルパークラスを適当に作って

ちょっとプロダクション寄りな擬似コード
    private fun setup() {
        //...

        bottomSheetHideableHelper = BottomSheetHideableHelper(bottomSheet)
        bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(view: View, newState: Int) {
                Log.d(LOG_TAG, "BottomSheetCallback#onStateChanged: newState=${newState}")
                bottomSheetHideableHelper.onStateChanged(newState)
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {

            }
        })
    }

    private fun showOrHideBottomSheet(show: Boolean) {
        if (show) {
            bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
            bottomSheetHideableHelper.setHideable(false)
        } else {
            bottomSheetHideableHelper.setHideable(true)
            bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
        }
    }

これで同じ内容の処理ができるはず。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2