ユーザー スケーラブルなコンテンツをサポートする

ピンチズーム ジェスチャーを実装して、アプリ内のスケーラブルなコンテンツをサポートします。これは、ユーザー補助を改善するための標準的なプラットフォームに一貫した方法であり、ユーザーはテキストや UI 要素のサイズをニーズに合わせて直感的に調整できます。アプリでは、ユーザーが画面拡大などのシステムレベルの機能よりも早く見つけられるようなエクスペリエンスを提供する、きめ細かい制御とコンテキスト ベースの動作でカスタム スケーリング動作を定義できます。

スケーリング戦略を選択する

このガイドで説明する戦略では、画面の幅に合わせて UI がリフローされ、再編成されます。これにより、長いテキスト行を読み取るために必要となる水平方向のパンや、煩わしい「ジグザグ」の動きが不要になるため、ユーザー補助機能の面で大きなメリットが得られます。

参考資料: 視力の低いユーザーにとって、コンテンツのリフローは、2 次元パンニングを必要とするインターフェースよりも、はるかに読みやすく、操作しやすいことが調査で確認されています。詳しくは、モバイル デバイスでのパン&スキャンとリフロー可能なコンテンツの比較をご覧ください。

すべての要素またはテキスト要素のみを拡大縮小する

次の表は、各スケーリング戦略の視覚効果を示しています。

戦略 密度スケーリング フォントの拡大縮小

動作

すべての要素の縦横比を維持して拡大縮小します。コンテンツがコンテナに合わせてリフローされるため、ユーザーはすべてのコンテンツを表示するために水平方向にパンする必要がありません。

テキスト要素にのみ影響します。レイアウト全体とテキスト以外のコンポーネントのサイズは変わりません。

What Scales

すべての視覚要素: テキスト、コンポーネント(ボタン、アイコン)、画像、レイアウトのスペーシング(パディング、マージン)

テキストのみ

デモ

推奨事項

視覚的な違いを確認したところで、次の表を参考に、トレードオフを比較検討して、コンテンツに最適な戦略を選択しましょう。

UI タイプ

推奨される戦略

推論

読み取り重視のレイアウト

例: ニュース記事、メッセージ アプリ

密度またはフォントのスケーリング

密度スケーリングは、インライン画像を含むコンテンツ領域全体をスケーリングする場合に推奨されます。

テキストのみをスケーリングする必要がある場合は、フォント スケーリングが簡単な代替手段となります。

視覚的に構造化されたレイアウト

例: アプリストア、ソーシャル メディア フィード

密度スケーリング

カルーセルやグリッド内の画像とテキストの視覚的な関係を維持します。リフローの性質により、ネストされたスクロール要素と競合する水平方向のパンを回避できます。

Jetpack Compose でスケーリング ジェスチャーを検出する

ユーザーがスケーリングできるコンテンツをサポートするには、まずマルチタッチ ジェスチャーを検出する必要があります。Jetpack Compose では、Modifier.transformable を使用してこれを行うことができます。

transformable 修飾子は、最後のジェスチャー イベントからの zoomChange デルタを提供する高レベルの API です。これにより、状態更新ロジックが直接的な累積(scale *= zoomChange など)に簡素化され、このガイドで説明するアダプティブ スケーリング戦略に最適になります。

次の例は、密度スケーリングとフォント スケーリングの戦略を実装する方法を示しています。

密度スケーリング

このアプローチでは、UI 領域のベース density をスケーリングします。その結果、画面サイズや解像度が変更されたかのように、パディング、スペーシング、コンポーネント サイズなど、レイアウト ベースの測定値がすべてスケーリングされます。テキスト サイズは密度にも依存するため、テキスト サイズも比例してスケーリングされます。この戦略は、UI の全体的な視覚的リズムとプロポーションを維持しながら、特定の領域内のすべての要素を均一に拡大する場合に効果的です。

private class DensityScalingState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledDensity(): Density {
        return Density(
            currentDensity.density * scaleFactor.floatValue,
            currentDensity.fontScale
        )
    }
}

フォントの拡大縮小

この戦略はより的を絞ったもので、fontScale 係数のみを変更します。その結果、テキスト要素のみが拡大縮小され、コンテナ、パディング、アイコンなどの他のすべてのレイアウト コンポーネントは固定サイズになります。この戦略は、読み取りを多用するアプリでテキストの読みやすさを向上させるのに適しています。

class FontScaleState(
    // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x.
    private val minScale: Float = 0.75f,
    private val maxScale: Float = 3.5f,
    private val currentDensity: Density
) {
    val transformableState = TransformableState { zoomChange, _, _ ->
        scaleFactor.floatValue =
            (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale)
    }
    val scaleFactor = mutableFloatStateOf(1f)
    fun scaledFont(): Density {
        return Density(
            currentDensity.density,
            currentDensity.fontScale * scaleFactor.floatValue
        )
    }
}

共有デモ UI

これは、前の 2 つの例で異なるスケーリング動作をハイライトするために使用される共有 DemoCard コンポーザブルです。

@Composable
private fun DemoCard() {
    Card(
        modifier = Modifier
            .width(360.dp)
            .padding(16.dp),
        shape = RoundedCornerShape(12.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text("Demo Card", style = MaterialTheme.typography.headlineMedium)
            var isChecked by remember { mutableStateOf(true) }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge)
                Switch(checked = isChecked, onCheckedChange = { isChecked = it })
            }
            Row(verticalAlignment = Alignment.CenterVertically) {
                Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp))
                Spacer(Modifier.width(8.dp))
                Text("Demo Icon", style = MaterialTheme.typography.bodyLarge)
            }
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Blue)
                )
                Box(
                    Modifier
                        .width(100.dp)
                        .weight(1f)
                        .height(80.dp)
                        .background(Color.Red)
                )
            }
            Text(
                "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," +
                    " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Justify
            )
        }
    }
}

ヒントと考慮事項

より洗練されたアクセシビリティの高いエクスペリエンスを作成するには、次の推奨事項を検討してください。

  • ジェスチャー以外のスケール コントロールの提供を検討する: ジェスチャーが苦手なユーザーもいます。このようなユーザーをサポートするために、ジェスチャーに依存しない体重計の調整またはリセットの代替方法を提供することを検討してください。
  • すべてのスケールに対応したビルド: アプリ内スケーリングとシステム全体のフォントまたは表示設定の両方に対して UI をテストします。アプリのレイアウトが、コンテンツを壊したり、重複させたり、隠したりすることなく、正しく適応していることを確認します。アダプティブ レイアウトの作成方法について学習する。