
エクストーン Androidエンジニアの市橋です。
今回はJetpack Composeを使ったAndroid開発において、ボトムシートのタッチイベントの競合について書こうと思います。
つい先日、弊社案件の開発中にModalBottomSheet内で横スクロールしようとすると、ボトムシート自体が勝手に閉じてしまう現象が発生しました。
この問題の調査をしていく中で、実は解決方法がとてもシンプルであることがわかりました。
なので、この記事でModalBottomSheetのタッチイベント競合の原因と解決方法を簡潔に紹介します。
原因
結論、Composeにおけるタッチイベントの伝播の仕組みに原因がありました。
Composeでは、タッチイベントは子要素から親要素へ伝播します。親要素がこのタッチイベントを「ドラッグ」として受け取ってしまうと、ModalBottomSheetはそのドラッグ動作を「閉じる動作」として認識してしまいます。
例えば、具体的な流れはこんな感じです。
- ユーザーが
ModalBottomSheet内の子要素をスクロールする - 子要素がタッチイベントを検知する
- 2のイベントが親要素の
ModalBottomSheetにも伝わる - 親要素の
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をより最大限活用できるように粛々と励んでいきたいと思います。ありがとうございました。