多くのコンポーザブルには、タップまたはクリックのサポートが組み込まれており、onClick
ラムダが含まれています。たとえば、サーフェスの操作に適したマテリアル デザインのすべての動作を含むクリック可能な Surface
を作成できます。
Surface(onClick = { /* handle click */ }) { Text("Click me!", Modifier.padding(24.dp)) }
しかし、ユーザーがコンポーザブルを操作する方法はクリックだけではありません。このページでは、1 つのポインタを含む操作(そのイベントの処理にとってポインタの位置は重要ではない)に焦点を当てます。次の表に、こうした操作の種類を示します。
ジェスチャー |
説明 |
タップ(またはクリック) |
ポインタが下に下がってから上がる |
ダブルタップ |
ポインタが下、上、下、上に移動 |
長押し |
ポインタが下がり、長押しされる時間が長くなる |
プレスルーム |
ポインタが下に移動する |
タップまたはクリックに反応する
clickable
は、コンポーザブルをタップやクリックに反応させる一般的な修飾子です。また、この修飾子により、フォーカス、マウスとタッチペンでのホバーのサポート、押下時のカスタマイズ可能な視覚的インジケーターなどの追加機能も追加されます。この修飾子は、広い意味で「クリック」に応答します。マウスや指によるものだけでなく、キーボード入力によるクリック イベントやユーザー補助サービスを使用した場合にも、クリック イベントに応答します。
ユーザーが画像をクリックすると、画像が全画面表示される、画像のグリッドがあるとします。
グリッド内の各アイテムに clickable
修飾子を追加すると、この動作を実装できます。
@Composable private fun ImageGrid(photos: List<Photo>) { var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) } LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) { items(photos, { it.id }) { photo -> ImageItem( photo, Modifier.clickable { activePhotoId = photo.id } ) } } if (activePhotoId != null) { FullScreenImage( photo = photos.first { it.id == activePhotoId }, onDismiss = { activePhotoId = null } ) } }
clickable
修飾子を使用すると、次のような動作も追加されます。
interactionSource
とindication
。ユーザーがコンポーザブルをタップしたときに、デフォルトで波紋を描画します。カスタマイズ方法については、ユーザー操作の処理ページをご覧ください。- セマンティクス情報を設定して、ユーザー補助サービスが要素とやり取りできるようにします。
- フォーカスを外して
Enter
または D-pad の中央を押すことで、キーボードやジョイスティックの操作をサポートします。 - 要素がマウスオーバー可能になるようにし、マウスまたはタッチペンでカーソルを合わせると反応するようにします。
長押しするとコンテキスト コンテキスト メニューが表示されます
combinedClickable
を使用すると、通常のクリック動作に加えて、ダブルタップまたは長押しの動作を追加できます。combinedClickable
を使用すると、ユーザーがグリッド画像を長押ししたときにコンテキスト メニューを表示できます。
var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) } val haptics = LocalHapticFeedback.current LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) { items(photos, { it.id }) { photo -> ImageItem( photo, Modifier .combinedClickable( onClick = { activePhotoId = photo.id }, onLongClick = { haptics.performHapticFeedback(HapticFeedbackType.LongPress) contextMenuPhotoId = photo.id }, onLongClickLabel = stringResource(R.string.open_context_menu) ) ) } } if (contextMenuPhotoId != null) { PhotoActionsSheet( photo = photos.first { it.id == contextMenuPhotoId }, onDismissSheet = { contextMenuPhotoId = null } ) }
ユーザーが要素を長押ししたときは触覚フィードバックを含めることをおすすめします。そのため、スニペットには performHapticFeedback
の呼び出しが含まれています。
スクリムをタップしてコンポーザブルを閉じる
上記の例では、clickable
と combinedClickable
がコンポーザブルに便利な機能を追加しています。インタラクションを視覚的に示し、カーソルを合わせると応答します。また、フォーカス、キーボード、ユーザー補助のサポートを備えています。ただし、この追加動作が必ずしも望ましいわけではありません。
[画像の詳細] 画面を見てみましょう。背景は半透明にして、ユーザーがその背景をタップして詳細画面を閉じることができるようにする必要があります。
この場合、その背景にはインタラクションを視覚的に示さず、カーソルを合わせたときやフォーカス可能にしないでください。また、キーボードやユーザー補助イベントに対するレスポンスは、通常のコンポーザブルの応答とは異なります。clickable
の動作を適応させる代わりに、下位の抽象化レベルにプルダウンして、pointerInput
修飾子を detectTapGestures
メソッドと組み合わせて使用します。
@OptIn(ExperimentalComposeUiApi::class) @Composable private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) { val strClose = stringResource(R.string.close) Box( modifier // handle pointer input .pointerInput(onClose) { detectTapGestures { onClose() } } // handle accessibility services .semantics(mergeDescendants = true) { contentDescription = strClose onClick { onClose() true } } // handle physical keyboard input .onKeyEvent { if (it.key == Key.Escape) { onClose() true } else { false } } // draw scrim .background(Color.DarkGray.copy(alpha = 0.75f)) ) }
pointerInput
修飾子のキーとして、onClose
ラムダを渡します。これにより、自動的にラムダが再実行され、ユーザーがスクリムをタップしたときに適切なコールバックが呼び出されるようになります。
ダブルタップすると拡大できます
clickable
と combinedClickable
には、適切な方法でインタラクションに応答するのに十分な情報が含まれていない場合があります。たとえば、コンポーザブルは、操作が行われたコンポーザブルの境界内の位置にアクセスする必要があります。
もう一度画像の詳細画面を見てみましょう。ダブルタップすると、画像を拡大できるようにすることをおすすめします。
動画からわかるように、タップイベントの位置の周辺でズームインが発生します。画像の左側と右側でズームインすると、結果は異なります。pointerInput
修飾子を detectTapGestures
と組み合わせて使用すると、タップ位置を計算に組み込むことができます。
var zoomed by remember { mutableStateOf(false) } var zoomOffset by remember { mutableStateOf(Offset.Zero) } Image( painter = rememberAsyncImagePainter(model = photo.highResUrl), contentDescription = null, modifier = modifier .pointerInput(Unit) { detectTapGestures( onDoubleTap = { tapOffset -> zoomOffset = if (zoomed) Offset.Zero else calculateOffset(tapOffset, size) zoomed = !zoomed } ) } .graphicsLayer { scaleX = if (zoomed) 2f else 1f scaleY = if (zoomed) 2f else 1f translationX = zoomOffset.x translationY = zoomOffset.y } )
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- ジェスチャーについて
- Compose のマテリアル デザイン 2
- Jetpack Compose で Kotlin を使用する