BottomNavigationViewの上にSnackbarが表示されるようにしつつFABも連動して動くようにする
BottomNavigationViewを使ってみようかなと思ったときに、ふと「Snackbarはどこに表示されるのが正しいのか」ということを疑問に思った。ガイドラインではBottomNavigationViewの上からSnackbarが現れるようにするということが書いてあった。
https://material.io/guidelines/components/bottom-navigation.html#bottom-navigation-specs
Elevation的にはSnackbarがBottomNavigationViewより下にあるので、「下に配置する」というべきなんで、上から現れると表現するのも誤解がありそうな気がして気持ち悪い。
挙動はわかったが、ではそれをどうやって実装すればよいのかという話になると、これがややこしい。いろいろ探し回ったが、こちらのサイトを参考にするのが良さそうな感じであった。
https://sakebook.hatenablog.com/entry/2017/02/12/032501
結論から言うとCoordinatorLayout.Behaviorを継承して、カスタムビヘイビアを使って実装するしかないようだ。今のところは。それとも、もしかしたら、私が見つけられなかっただけで、もっと簡単な方法があるのかもしれない。
BottomNavigationViewを配置して、さらにFABも一緒に配置したい場合はどうするのか。
つまりこういう動きをしたい、ということである。
- SnackbarはBNVの上辺から現れる
- FABはSnackbarを避ける
- SnackbarはBNVの動きに合わせて動く=FABも連動して動く
- FABはBNVも避ける
- BNVはスクロールに合わせて隠れる(Appbarが隠れるのと連動する)
単純にSnackbarがBNVの上辺から出現してくれれば(SnackbarがBNVを避けてくれれば)ことは簡単なのだが、そういう設定にたどり着くことができず、最終的にcustom behaviorでゴリ押しした。
コードはGitHubにあげておいた。
これは原理をいまだ理解していないのだが、BNVにapp:insetEdge="bottom"
を加えることでFABがBNVを避けるようになる。
これに気づくまでが非常に長くて、ここで俺の苦労を聞いてくれと言いたいところだが割愛する。とりあえず、FABがSnackbarを避けるのはBehaviorによるものではなかったというのが今回の作業で得られたもっとも大きな収穫かもしれない。
insetEdge
の挙動に詳しい人、もしくは詳しく解説したブログ記事なんかをご存じの方は教えて欲しい。
スクロールに合わせてBNVを隠す。
このあたりからこちらのサイトを参考にしだす。
https://sakebook.hatenablog.com/entry/2017/02/12/032501
私はAppbarLayoutが隠れている比率を計算して、同じ比率だけBNVを隠すという実装を行った。最初はAppbarLayoutのBehaviorを真似しようと思ったが、ややこしかったので途中で諦めた。
ちなみにAppbarLayoutを動かさないで、この仕様を取り入れたいという場合は、onNestedScroll
などを使って自分で隠すようにする必要があるだろう。
この実装にしたのはその手動計算が面倒くさかったというのもある。
やり方としては
- custom behaviorで`layoutDependsOn`を使いAppbarLayoutに依存するように宣言
- `onDependentViewChanged`でAppbarLayoutがどれだけ隠れているかを計算する
- 同メソッド内でBNVの`setTranslationY`を使ってBNVを隠す
これが一番苦労した。参考にしたサイトでは、Snackbar表示中はBNVを動かさない、というやり方での実装だった。私の場合はSnackbar表示中だろうとBNVは動くし、それに合わせてSnackbarも動く。
- custom behaviorで`oayoutDependsOn`を使いSnackbar.SnackbarLayoutに依存するよう宣言
- `onDependentViewChanged`でSnackbarが出現したことをフラグで持つ
- Snackbar表示中は、`onNestedPreScroll`でSnackbarのpaddingを更新する
- `onDependentViewRemoved`でSnackbarが消えたらフラグをクリアする
BNVの動きに連動してSnackbarのpaddingを更新しなければならないので、こんな変な実装になってしまった。
BNVのbehaviorがSnackbarの動きを制御するという若干の気持ち悪さがあるが、他に方法を思いつかなかった。
と思っていろいろ試したのだけど、結局良くわからなかったのでこのような実装になった。
insetEdge
のinsetが何のことかよくわかっていない。似たようなやつにdodgeInsetEdge
なるものもある。dodgeInsetEdge="bottom"
を設定したら、画面上部に向かってViewが飛んでいって、呪いの館を思い出した。
insetEdge
の使い方を詳しく解説しているサイトをご存知だったら教えて欲しい。
コードの全体(といっても、重要なのはcustom behaviorだけ)はGitHubにあるので参照してほしい。
ちなみにこのコードはsupport library 25.3.1で動作確認している。バージョンによって挙動が変わると思うので、注意してほしい。