Compose UIでのタッチイベント競合を一発解消するテクニック

エクストーン Androidエンジニアの市橋です。

今回はJetpack Composeを使ったAndroid開発において、ボトムシートのタッチイベントの競合について書こうと思います。

developer.android.com

m3.material.io

つい先日、弊社案件の開発中にModalBottomSheet内で横スクロールしようとすると、ボトムシート自体が勝手に閉じてしまう現象が発生しました。

この問題の調査をしていく中で、実は解決方法がとてもシンプルであることがわかりました。

なので、この記事でModalBottomSheetのタッチイベント競合の原因と解決方法を簡潔に紹介します。

原因

結論、Composeにおけるタッチイベントの伝播の仕組みに原因がありました。

Composeでは、タッチイベントは子要素から親要素へ伝播します。親要素がこのタッチイベントを「ドラッグ」として受け取ってしまうと、ModalBottomSheetはそのドラッグ動作を「閉じる動作」として認識してしまいます。

例えば、具体的な流れはこんな感じです。

  1. ユーザーがModalBottomSheet内の子要素をスクロールする
  2. 子要素がタッチイベントを検知する
  3. 2のイベントが親要素のModalBottomSheetにも伝わる
  4. 親要素のModalBottomSheetは「ドラッグされた」と解釈して閉じる

対策

解決方法は、ComposeのModifierを活用して、タッチイベントを子要素内で消費することで、親要素への伝播を防ぐことです。

これにより、親要素であるModalBottomSheetはタッチイベントを認識せず、ボトムシートが閉じる動作が発生しなくなります。

以下は対策したサンプルコードです↓

@Composable
fun ScrollableContent() {
    val scrollState = rememberScrollState()

    Row(
        modifier = Modifier
            .pointerInput(Unit) {
                detectHorizontalDragGestures { change, dragAmount ->
                    // スクロール状態を手動で更新
                    scrollState.dispatchRawDelta(-dragAmount)
                    // ジェスチャーを消費して親への伝播を防ぐ
                    change.consume()
                }
            }
    ) {
        // ここにスクロールするコンテンツを配置
    }
}

まずはModifier.pointerInput()と水平方向のドラッグ動作を検知するdetectHorizontalDragGesturesの2つを用いたカスタムのジェスチャー検知ロジックを追加します。

因みに、垂直方向にはdetectVerticalDragGesturesを用います。

その中で、

  • 手動によるスクロール位置の更新
  • ジェスチャーの消費

の2つの処理を行うだけになります。

手動でスクロール位置の更新をする必要があるのは、detectHorizontalDragGestures(もしくはdetectVerticalDragGestures)を使ってドラッグイベントを独自に処理している関係で、Composeの通常のスクロール処理が行われないためです。そこで、ドラッグ量(dragAmount)に合わせてscrollStateを自力で更新し、スクロール動作を反映させます。

あとは、ジェスチャーの入力変化(change)に対してconsume()を呼び出し、子要素内でジェスチャーイベントが完結するようにします。

以上で解決です🎉

おわりに

今回は、Jetpack Composeにおけるボトムシート内のタッチイベントの競合問題についてサクッとまとめました。

一見厄介なバグに見えるかもしれませんが、解決方法は非常にシンプルであることが読み取れるかと思います。

なので、何も恐れることはないんです👍

また、他のComposeにおけるタッチイベント競合、もしくは競合の重複についても、根本の原因と対策は全く同じです。

Composeで作るUIにおけるユーザー操作で思わぬ動作になる場合には、今回のテクニックもぜひ参考にしてみてください。

自分もまた弊社でComposeをより最大限活用できるように粛々と励んでいきたいと思います。ありがとうございました。