[Android] BottomSheetDialog にフッターを設定する

この記事は [Android Advent Calendar 2023](https://qiita.com/advent-calendar/2023/android) 24日目の記事…です。


## フッターを設定する
レイアウトファイルでいい感じに設定する…のはできないので、フッターの位置を計算して表示する必要があります。

`addBottomSheetCallback`でスライドしたことをハンドリングすることができます。

```kotlin:FirstBottomSheetDialogFragment.kt
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val bottomSheetDialog = BottomSheetDialog(requireContext())
    val fullScreenHeight = getFullScreenHeight()

    bottomSheetDialog.setOnShowListener { dialog ->
        (dialog as? BottomSheetDialog)?.findViewById<FrameLayout>(R.id.design_bottom_sheet)
            ?.let {
                BottomSheetBehavior.from(it).apply {
                    // ・・・
                    addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                        override fun onStateChanged(bottomSheet: View, newState: Int) {}

                        override fun onSlide(bottomSheet: View, slideOffset: Float) {
                            // ここでスライドしたことをハンドリングできる
                        }
                    })
                }
            }
    }

    return bottomSheetDialog
}

```
スライドされた時に表示位置を計算してフッターに設定したいViewのy座標に設定します。

```kotlin:onCreateDialog
// ・・・
addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
    override fun onStateChanged(bottomSheet: View, newState: Int) {}

    override fun onSlide(bottomSheet: View, slideOffset: Float) {
        val currentBottomSheetHeight = fullScreenHeight - bottomSheet.top
        val currentFooterHeight =
            currentBottomSheetHeight - binding.footerView.height

        binding.footerView.y = currentFooterHeight.toFloat()
    }
}.apply {
    binding.root.post { onSlide(binding.root.parent as View, 0f) }
})
// ・・・
```

### 結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346644/45d116ee-0ecf-91e7-c135-af6bca29aff0.gif" width="300" alt="フッターを設定したときのgif">

### Edge to edgeとの干渉
BottomSheetDialog は edge-to-edge の影響を受けます。
[公式のサイト](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetDialog)より、`R.attr.navigationBarColor` が transparent または translucent で、`enableEdgeToEdge`がtrueの時は自動的にEdge to edgeのwindow flagsが自動的に適用されます。これによって、BottomSheetDialogFragmentが全画面を覆う時、status bar以上の高さになると自動的にパディングが追加されます。

この追加されたパディングによって、フッターの計算がズレます。そのため、フッターを設定するのであればEdge to edgeにならないように設定を変更するか、BottomSheetDialogFragmentが全画面を覆わないように`maxHeight`をstatus barの高さを引いた画面の高さに設定する必要があります。

```kotlin
// ・・・
BottomSheetBehavior.from(it).apply {
    maxHeight = getFullScreenHeight() - getStatusBarHeight()
}
// ・・・
```

<details><summary>動画</summary><div>
こんな感じでstatus bar以上の高さになるとフッターがズレてしまう
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346644/6e457cf1-a5a5-8cf1-7de9-312b3ab5941c.gif" width="300" alt="フッターがズレるgif">


maxHeightをstatus barを引いた高さに設定するとこんな感じ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346644/8d7dcaa1-5ed6-7b9e-970c-77a6cca3c384.gif" width="300" alt="maxHeightを設定したgif">

</div></details>

ちなみに公式でもフッターを設定しているような BottomSheetDialog がありますが、内部にスクロールを入れて BottomSheetDialog自体がスクロールしないようになっていました。 その方が良いです。

## 参考
[Static Footer View On Android Bottom Sheet - Stack OverFlow](https://stackoverflow.com/questions/58292471/static-footer-view-on-android-bottom-sheet)
[BottomSheetBehavior - Android Developers](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetBehavior)