タップして押す

多くのコンポーザブルには、タップまたはクリックのサポートが組み込まれており、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 修飾子を使用すると、次のような動作も追加されます。

  • interactionSourceindication。ユーザーがコンポーザブルをタップしたときに、デフォルトで波紋を描画します。カスタマイズ方法については、ユーザー操作の処理ページをご覧ください。
  • セマンティクス情報を設定して、ユーザー補助サービスが要素とやり取りできるようにします。
  • フォーカスを外して 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 の呼び出しが含まれています。

スクリムをタップしてコンポーザブルを閉じる

上記の例では、clickablecombinedClickable がコンポーザブルに便利な機能を追加しています。インタラクションを視覚的に示し、カーソルを合わせると応答します。また、フォーカス、キーボード、ユーザー補助のサポートを備えています。ただし、この追加動作が必ずしも望ましいわけではありません。

[画像の詳細] 画面を見てみましょう。背景は半透明にして、ユーザーがその背景をタップして詳細画面を閉じることができるようにする必要があります。

この場合、その背景にはインタラクションを視覚的に示さず、カーソルを合わせたときやフォーカス可能にしないでください。また、キーボードやユーザー補助イベントに対するレスポンスは、通常のコンポーザブルの応答とは異なります。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 ラムダを渡します。これにより、自動的にラムダが再実行され、ユーザーがスクリムをタップしたときに適切なコールバックが呼び出されるようになります。

ダブルタップすると拡大できます

clickablecombinedClickable には、適切な方法でインタラクションに応答するのに十分な情報が含まれていない場合があります。たとえば、コンポーザブルは、操作が行われたコンポーザブルの境界内の位置にアクセスする必要があります。

もう一度画像の詳細画面を見てみましょう。ダブルタップすると、画像を拡大できるようにすることをおすすめします。

動画からわかるように、タップイベントの位置の周辺でズームインが発生します。画像の左側と右側でズームインすると、結果は異なります。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
        }
)