Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

Android Jetpack Compose でスクロールと同期して Bottom Navigation の表示を切り替える

こんにちは。Android アプリ開発担当の nagayama(@nagayan_dev)です。
今回は Jetpack Compose で下記のようなスクロールと同期して Bottom Navigation の表示を切り替えるレイアウトを作っていきます。

仕様

今回は下記の仕様で実装していきたいと思います。

  • 下にスクロールした時、 Bottom Navigation を下にスライドさせて非表示にする
  • 上にスクロールした時、 Bottom Navigation を表示させる
  • 一番下までスクロールした時、 Bottom Navigation を表示させる



実装

完成したコードは下記のようになります。(一部処理は省略しています。)

@Composable
fun Page() {
    var isShowBottomNavigation by remember { mutableStateOf(true) }
    var webViewState by remember { mutableStateOf<WebView?>(null) }

    // スクロール時に表示切り替えをする
    val transition = updateTransition(targetState = isShowBottomNavigation, label = LABEL)
    val transitionValue by transition.animateDp(label = LABEL) {
        if (it) 0.dp else -BOTTOM_NAVIGATION_HEIGHT
    }

    Box(
            modifier = Modifier
                    .fillMaxSize(),
    ) {
        val context = LocalContext.current
        AndroidView(
                factory = {
                    val webView = WebView(context)
                    webView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
                        val isScrollOver = !webView.canScrollVertically(1)
                        val value = if (isScrollOver) {
                            // 画面一番下まで到達した時
                            true
                        } else {
                            scrollY < oldScrollY
                        }
                        if (isShowBottomNavigation != value) {
                            isShowBottomNavigation = value
                        }
                    }
                    webViewState = webView
                    webView
                },
                update = {
                    it.loadUrl(PRIVACY_POLICY_URL)
                },
                modifier = Modifier
                        .fillMaxSize()
        )
        BottomNavigation(
                modifier = Modifier
                        .fillMaxWidth()
                        .height(BOTTOM_NAVIGATION_HEIGHT)
                        .align(Alignment.BottomCenter)
                        .absoluteOffset(y = -transitionValue)
        )
    }
}

こちらを元に解説をしていきます。

アニメーションの実装

今回は Jetpack Compose の updateTransition アニメーションを使用していきたいと思います。

developer.android.com

updateTransition は、指定された enum 型やデータ型の状態に応じて値を変化させることができるものになります。

今回は表示状態の State の isShowBottomNavigation を指定することで、表示状態・非表示状態で Bottom Navigation の表示位置の値を変化させようとします。今回は表示状態では 0.dp 、非表示状態ではマイナスの Bottom Navigation の高さになるように実装しました。

var isShowBottomNavigation by remember { mutableStateOf(true) }
val transition = updateTransition(targetState = isShowBottomNavigation, label = LABEL)
val transitionValue by transition.animateDp(label = LABEL) {
    if (it) 0.dp else BOTTOM_NAVIGATION_HEIGHT
}

そしてその変化する値を Bottom Navigation に設定します。Modifier の absoluteOffset の y に値を指定します。これにより表示状態では何も起こらず、非表示状態が指定された時に Bottom Navigation の高さ分下方向にズレるため見えなくなります。

BottomNavigation(
    modifier = Modifier
        ~~~
        .absoluteOffset(y = transitionValue)


スクロールイベント

続けて表示状態を切り替える契機となる、スクロールイベントとの繋ぎ込みを行います。今回は WebView をスクロールすることで、 Bottom Navigation の表示を切り替えるようにしたいと思います。

webView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> 
    val isScrollOver = !webView.canScrollVertically(1)
    val value = if (isScrollOver) {
        // 画面一番下まで到達した時
        true
    } else {
        scrollY < oldScrollY
    }
    if (isShowBottomNavigation != value) {
        isShowBottomNavigation = value
    }
}

スクロールイベントをキャッチするために、 WebView の setOnScrollChangeListener を使用します。そこの scrollY と oldScrollY の値を比較して、スクロール方向を算出します。また canScrollVertically を使用してそれ以上スクロールができるかどうかの判定を取得します。引数に 1 を指定することで「下方向にスクロールできるか」が取得でき、その反対で「これ以上下方向にスクロールできない(一番下まで達した)」の判定ができるようになります。

仕様に合わせて Bottom Navigation を表示するかどうかの判定が取れたら、 isShowBottomNavigation にその値を反映させます。ここで変更があれば先ほどのアニメーションで変更がキャッチされ、 Bottom Navigation のアニメーションが開始されます。


まとめ

今回はスクロールと同期して Bottom Navigation の表示を切り替えるレイアウトを作成しました。Jetpack Compose の前まででは HideBottomViewOnScrollBehavior を用いることで簡単に実装ができていましたが、Jetpack Compose では自分で 1 から実装が必要になります。手間がかかる分イベントがキャッチできたりカスタマイズができる等、自由度がかなり上がるので実装をしていて楽しくなります。次は動画に挑戦します。