操作

Compose には、ユーザー インタラクションによって生成された操作の検出に役立つさまざまな API が用意されています。幅広いユースケースに対応する API があります。

  • その一部は高レベルの API で、よく使われる操作をカバーできるように設計されています。たとえば、clickable 修飾子を使用すると、クリックの検出が容易になり、タップ時にユーザー補助機能を提供するとともに、視覚的インジケーター(リップルなど)を表示できます。

  • また、PointerInputScope.detectTapGesturesPointerInputScope.detectDragGestures など、あまり使われない操作の検出機能もあり、低レベルで柔軟性を高めています。ただし、これらは追加の機能は含んでいません。

タップと押下

clickable 修飾子を使用すると、アプリはそれが適用された要素のクリックを検出できます。

@Composable
fun ClickableSample() {
    val count = remember { mutableStateOf(0) }
    // content that you want to make clickable
    Text(
        text = count.value.toString(),
        modifier = Modifier.clickable { count.value += 1 }
    )
}

タップに応答する UI 要素の例

もっと柔軟性が必要な場合は、pointerInput 修飾子を使用してタップ操作検出機能を提供できます。

Modifier.pointerInput(Unit) {
    detectTapGestures(
        onPress = { /* Called when the gesture starts */ },
        onDoubleTap = { /* Called on Double Tap */ },
        onLongPress = { /* Called on Long Press */ },
        onTap = { /* Called on Tap */ }
    )
}

スクロール

Scroll 修飾子

verticalScroll 修飾子と horizontalScroll 修飾子は、コンテンツの境界が最大サイズ制約より大きい場合にユーザーが要素をスクロールできるようにする最も簡単な方法を提供します。verticalScroll 修飾子と horizontalScroll 修飾子では、コンテンツを変換またはオフセットする必要はありません。

@Composable
fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

スクロール操作に応答するシンプルな垂直方向のリスト

ScrollState により、スクロール位置を変更したり、現在の状態を取得したりできます。デフォルトのパラメータでこれを作成するには、rememberScrollState() を使用します。

@Composable
private fun ScrollBoxesSmooth() {

    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Scrollable 修飾子

scrollable 修飾子が scroll 修飾子と異なる点は、scrollable はスクロール操作を検出するが、そのコンテンツをオフセットしないことです。この修飾子が正しく動作するためには、ScrollableController が必要です。ScrollableController を作成する際は、各スクロール ステップで(操作入力、スムーズ スクロール、またはフリングによって)呼び出される consumeScrollDelta 関数をピクセル単位のデルタで提供する必要があります。適切なイベント伝播を確実に行うためには、消費されたスクロール距離の数値をこの関数から返す必要があります。

次のスニペットは、操作を検出してオフセットの数値を表示しますが、要素のオフセットは行いません。

@Composable
fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

指の押下を検出して指の位置の数値を表示する UI 要素

ネスト スクロール

Compose は、複数の要素が単一のスクロール操作に反応する「ネスト スクロール」をサポートしています。ネスト スクロールの一般的な例としては、別のリストに含まれるリストがあります。より複雑なケースとしては、折りたたみツールバーがあります。

自動ネスト スクロール

シンプルなネスト スクロールでは、アプリ側のアクションは必要ありません。スクロール アクションを開始する操作は、子から親に自動的に伝播されます。これにより、子がそれ以上スクロールできなくなると、親要素によって操作が処理されます。

次の例では、verticalScroll 修飾子が適用されているコンテナ内の要素にも verticalScroll 修飾子が適用されています。

val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
Box(
    modifier = Modifier
        .background(Color.LightGray)
        .verticalScroll(rememberScrollState())
        .padding(32.dp)
) {
    Column {
        repeat(6) {
            Box(
                modifier = Modifier
                    .height(128.dp)
                    .verticalScroll(rememberScrollState())
            ) {
                Text(
                    "Scroll here",
                    modifier = Modifier
                        .border(12.dp, Color.DarkGray)
                        .background(brush = gradient)
                        .padding(24.dp)
                        .height(150.dp)
                )
            }
        }
    }
}

内部要素の内側と外側の操作に応答する、2 つのネストされた垂直方向スクロールの UI 要素

nestedScroll 修飾子を使用する

複数の要素の間で高度な調整を行うスクロールを作成する必要がある場合は、nestedScroll 修飾子を使用して、ネストされたスクロールの階層を定義することにより、柔軟性を高めることができます。

ドラッグ

draggable 修飾子は、単一方向のドラッグ操作に対する高レベルのエントリ ポイントであり、ドラッグ距離をピクセル単位で報告します。

この修飾子は scrollable と似ていますが、操作の検出のみを行う点に注意してください。たとえば、offset 修飾子を使用して要素を移動することにより、状態を保持して画面上で表現する必要があります。

var offsetX by remember { mutableStateOf(0f) }
Text(
    modifier = Modifier
        .offset { IntOffset(offsetX.roundToInt(), 0) }
        .draggable(
            orientation = Orientation.Horizontal,
            state = rememberDraggableState { delta ->
                offsetX += delta
            }
        ),
    text = "Drag me!"
)

ドラッグ操作全体を制御する必要がある場合は、pointerInput 修飾子を介してドラッグ操作検出機能を使用することを検討してください。

Box(modifier = Modifier.fillMaxSize()) {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }

    Box(
        Modifier
            .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
            .background(Color.Blue)
            .size(50.dp)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consumeAllChanges()
                    offsetX += dragAmount.x
                    offsetY += dragAmount.y
                }
            }
    )
}

指の押下によってドラッグされる UI 要素

スワイプ

swipeable 修飾子を使用すると、要素を離したときに、通常は 1 つの方向に定義された 2 つ以上のアンカー ポイントに向かって移動するアニメーションが表示されるような方法で、要素をドラッグできます。この修飾子は、「スワイプして閉じる」パターンを実装するためによく使用されます。

この修飾子は要素を移動せず、操作の検出のみを行う点に注意してください。たとえば、offset 修飾子を使用して要素を移動することにより、状態を保持して画面上で表現する必要があります。

スワイプ可能な状態は swipeable 修飾子で必須であり、rememberSwipeableState() で作成し、記憶することができます。この状態では、プログラムでアンカーへの移動のアニメーションを表示する便利なメソッドのセット(snapToanimateToperformFlingperformDrag を参照)と、ドラッグの進行状況を観測するためのプロパティも利用できます。

スワイプ操作は、FixedThreshold(Dp)FractionalThreshold(Float) のような各種のしきい値タイプを持つように構成できます。また、出発点と到着点のアンカー ポイントの組み合わせごとに、異なるスワイプ操作を構成することもできます。

柔軟性を高めるために、境界を超えてスワイプする際の resistance を構成できます。また、スワイプが位置の thresholds に達していなくても次の状態に移動するアニメーションを表示する velocityThreshold も構成できます。

@Composable
fun SwipeableSample() {
    val width = 96.dp
    val squareSize = 48.dp

    val swipeableState = rememberSwipeableState(0)
    val sizePx = with(LocalDensity.current) { squareSize.toPx() }
    val anchors = mapOf(0f to 0, sizePx to 1) // Maps anchor points (in px) to states

    Box(
        modifier = Modifier
            .width(width)
            .swipeable(
                state = swipeableState,
                anchors = anchors,
                thresholds = { _, _ -> FractionalThreshold(0.3f) },
                orientation = Orientation.Horizontal
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(squareSize)
                .background(Color.DarkGray)
        )
    }
}

スワイプ操作に応答する UI 要素

マルチタッチ: パン、ズーム、回転

パン、ズーム、回転に使用されるマルチタッチ操作を検出するには、transformable 修飾子を使用します。この修飾子は、それ自体では要素を変換せず、操作の検出のみを行います。

@Composable
fun TransformableSample() {
    // set up all transformation states
    var scale by remember { mutableStateOf(1f) }
    var rotation by remember { mutableStateOf(0f) }
    var offset by remember { mutableStateOf(Offset.Zero) }
    val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
        scale *= zoomChange
        rotation += rotationChange
        offset += offsetChange
    }
    Box(
        Modifier
            // apply other transformations like rotation and zoom
            // on the pizza slice emoji
            .graphicsLayer(
                scaleX = scale,
                scaleY = scale,
                rotationZ = rotation,
                translationX = offset.x,
                translationY = offset.y
            )
            // add transformable to listen to multitouch transformation events
            // after offset
            .transformable(state = state)
            .background(Color.Blue)
            .fillMaxSize()
    )
}

マルチタッチ操作(パン、ズーム、回転)に応答する UI 要素

ズーム、パン、回転を他の操作と組み合わせる必要がある場合は、PointerInputScope.detectTransformGestures 検出機能を使用できます。