Scroll 修飾子

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

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

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

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))
        }
    }
}

スクロール可能な領域の修飾子

scrollableArea 修飾子は、カスタムのスクロール可能なコンテナを作成するための基本的な構成要素です。scrollable修飾子よりも抽象化レベルが高く、操作のデルタの解釈、コンテンツのクリッピング、オーバースクロール効果などの一般的な要件を処理します。

scrollableArea はカスタム実装に使用されますが、標準のスクロール リストには通常、verticalScrollhorizontalScrollLazyColumn などのコンポーザブルのような既製のソリューションを使用することをおすすめします。これらの高レベルのコンポーネントは一般的なユースケースではよりシンプルで、scrollableArea を使用して構築されています。

scrollableArea 修飾子と scrollable 修飾子の違い

scrollableAreascrollable の主な違いは、ユーザーのスクロール操作の解釈方法にあります。

  • scrollable (生のデルタ): デルタは、画面上のユーザー入力(ポインタのドラッグなど)の物理的な動きを直接反映します。
  • scrollableArea (コンテンツ指向のデルタ): delta は、スクロール位置の選択された変更を表すようにセマンティクス的に反転され、ユーザーの操作に合わせてコンテンツが移動するように見えます。これは通常、ポインタの動きとは逆になります。

次のように考えてください。scrollable はポインタの移動方法を示しますが、 scrollableArea はそのポインタの動きを、一般的なスクロール可能なビュー内でコンテンツがどのように 移動するかに変換します。この反転により、標準のスクロール可能なコンテナを実装する際に scrollableArea の方が自然に感じられます。

次の表に、一般的なシナリオでのデルタの符号をまとめます。

ユーザー ジェスチャー

scrollable によって dispatchRawDelta に報告される デルタ

scrollableArea によって dispatchRawDelta に報告されるデルタ *

ポインタが に移動

ポインタが に移動

ポインタが に移動

正(RTL の場合は負)

ポインタが に移動

負(RTL の場合は正)

(*)scrollableArea のデルタの符号に関する注意点: scrollableAreaからのデルタの符号は、単純な反転ではありません。次の点を考慮してインテリジェントに判断されます。

  1. 向き: 縦または横。
  2. LayoutDirection: LTR または RTL(特に横スクロールの場合に重要)。
  3. reverseScrolling フラグ: スクロール方向が反転しているかどうか。

scrollableArea は、スクロールのデルタを反転するだけでなく、コンテンツをレイアウトの境界にクリップし、オーバースクロール効果のレンダリングを処理します。デフォルトでは、LocalOverscrollFactory によって提供される効果を使用します。 OverscrollEffect パラメータを受け取る scrollableArea オーバーロード を使用すると、これをカスタマイズまたは無効にできます。

scrollableArea 修飾子を使用する場合

horizontalScroll 修飾子、verticalScroll 修飾子、Lazy レイアウトでは適切に処理できないカスタムのスクロール コンポーネントを構築する必要がある場合は、scrollableArea 修飾子を使用する必要があります。多くの場合、次のようなケースが該当します。

  • カスタム レイアウト ロジック: アイテムの配置が動的に変化する場合 スクロール位置に基づいて。
  • 独自の視覚効果: 変換、スケーリング、その他の効果を スクロール時に子に適用する場合。
  • 直接制御: スクロール メカニズムを細かく制御する必要がある場合 verticalScroll や Lazy レイアウトで公開されている以上の機能が必要な場合。

scrollableArea を使用してホイールのようなカスタムリストを作成する

次のサンプルでは、scrollableArea を使用して、中央から離れるにつれてアイテムが縮小するカスタムの垂直リストを構築し、ホイールのような視覚効果を作成しています。このようなスクロール依存の変換は、scrollableArea の最適なユースケースです。

図 2. scrollableArea を使用したカスタマイズされた垂直リスト。

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

Scrollable 修飾子

scrollable 修飾子が scroll 修飾子と異なる点は、scrollable は スクロール操作を検出してデルタをキャプチャするが、そのコンテンツを 自動的にオフセットしないことです。これは代わりに、この修飾子が正しく機能するために必要な ScrollableState を介してユーザーに委任されます。

ScrollableState を作成する際は、各スクロール ステップで(操作入力、スムーズ スクロール、またはフリングによって)呼び出される consumeScrollDelta 関数をピクセル単位のデルタで提供する必要があります。この関数は、scrollable 修飾子を持つネスト要素がある場合にイベントが適切に伝播されるように、消費したスクロール距離の量を返します。

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

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(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 要素
図 3.指の押下を検出して指の位置の数値を表示する UI 要素。