Supportare contenuti scalabili dall'utente

Implementa i gesti di pizzicamento per lo zoom per supportare i contenuti scalabili nella tua app. Questo è il metodo standard e coerente con la piattaforma per migliorare l'accessibilità, consentendo agli utenti di regolare in modo intuitivo le dimensioni del testo e degli elementi UI in base alle loro esigenze. La tua app può definire un comportamento di ridimensionamento personalizzato con un controllo granulare e un comportamento contestuale che offre un'esperienza che gli utenti spesso scoprono più rapidamente rispetto a una funzionalità a livello di sistema come l'ingrandimento dello schermo.

Scegli una strategia di scalabilità

Le strategie descritte in questa guida fanno sì che la UI venga ridisposta e riorganizzata per adattarsi alla larghezza dello schermo. Ciò offre un vantaggio significativo in termini di accessibilità, eliminando la necessità di eseguire la panoramica orizzontale e il frustrante movimento a "zig-zag" che altrimenti sarebbe necessario per leggere lunghe righe di testo.

Ulteriori letture: la ricerca conferma che per gli utenti con ipovisione, i contenuti con ridisposizione sono molto più leggibili e facili da navigare rispetto alle interfacce che richiedono la panoramica bidimensionale. Per maggiori dettagli, consulta A Comparison of Pan-and-Scan and Reflowable Content on Mobile Devices.

Scala tutti gli elementi o solo gli elementi di testo

La tabella seguente mostra l'effetto visivo di ogni strategia di scalabilità.

Strategia Scalabilità della densità Ridimensionamento carattere

Comportamento

Scala tutti gli elementi in modo proporzionale. I contenuti vengono ridisposti per adattarsi al contenitore, in modo che l'utente non debba spostarsi orizzontalmente per visualizzarli tutti.

Interessa solo gli elementi di testo. Il layout generale e i componenti non di testo rimangono delle stesse dimensioni.

Cosa scala

Tutti gli elementi visivi: testo, componenti (pulsanti, icone), immagini e spaziatura del layout (spaziatura interna, margini)

Solo testo

Dimostrazione

Consigli

Ora che hai visto le differenze visive, la seguente tabella ti aiuta a valutare i compromessi e a scegliere la strategia migliore per i tuoi contenuti.

Tipo di UI

Strategia consigliata

Ragionamento

Layout con molto testo

Esempi: articoli, app di messaggistica

Densità o ridimensionamento dei caratteri

La scalabilità della densità è preferibile per ridimensionare l'intera area dei contenuti, incluse le immagini incorporate.

La scalabilità dei caratteri è un'alternativa semplice se è necessario ridimensionare solo il testo.

Layout strutturati visivamente

Esempi: store, feed di social media

Scalabilità della densità

Conserva le relazioni visive tra immagini e testo in caroselli o griglie. La natura dinamica evita la panoramica orizzontale, che entrerebbe in conflitto con gli elementi di scorrimento nidificati.

Rileva i gesti di ridimensionamento in Jetpack Compose

Per supportare i contenuti scalabili dall'utente, devi prima rilevare i gesti multi-touch. In Jetpack Compose, puoi farlo utilizzando Modifier.transformable.

Il modificatore transformable è un'API di alto livello che fornisce il delta zoomChange dall'ultimo evento di gesto. In questo modo, la logica di aggiornamento dello stato viene semplificata in un'accumulazione diretta (ad esempio, scale *= zoomChange), il che la rende ideale per le strategie di scalabilità adattiva trattate in questa guida.

Esempi di implementazioni

I seguenti esempi mostrano come implementare le strategie di scalabilità della densità e dei caratteri.

Scalabilità della densità

Questo approccio scala la base density di un'area della UI. Di conseguenza, tutte le misurazioni basate sul layout, tra cui spaziatura interna, spaziatura e dimensioni dei componenti, vengono ridimensionate, come se le dimensioni o la risoluzione dello schermo fossero cambiate. Poiché le dimensioni del testo dipendono anche dalla densità, anche queste vengono ridimensionate in modo proporzionale. Questa strategia è efficace quando vuoi ingrandire in modo uniforme tutti gli elementi all'interno di un'area specifica, mantenendo il ritmo visivo e le proporzioni complessive della 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
        )
    }
}

@Composable
fun DensityScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState =
        remember(currentDensity) { DensityScalingState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledDensity()
        ) {
            DemoCard()
        }
    }
}

Ridimensionamento carattere

Questa strategia è più mirata e modifica solo il fattore fontScale. Il risultato è che solo gli elementi di testo aumentano o diminuiscono di dimensioni, mentre tutti gli altri componenti del layout, come contenitori, spaziatura interna e icone, rimangono di dimensioni fisse. Questa strategia è adatta per migliorare la leggibilità del testo nelle app con un'alta intensità di lettura.

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

@Composable
fun FontScalingSample() {
    val currentDensity = LocalDensity.current
    val scaleState = remember { FontScaleState(currentDensity = currentDensity) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .transformable(state = scaleState.transformableState),
        contentAlignment = Alignment.TopCenter
    ) {
        CompositionLocalProvider(
            LocalDensity provides scaleState.scaledFont()
        ) {
            DemoCard()
        }
    }
}

UI della demo condivisa

Questo è il composable DemoCard condiviso utilizzato da entrambi gli esempi precedenti per evidenziare i diversi comportamenti di scalabilità.

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

Suggerimenti e considerazioni

Per creare un'esperienza più rifinita e accessibile, tieni presente i seguenti suggerimenti:

  • Valuta la possibilità di offrire controlli di ridimensionamento non basati su gesti: alcuni utenti potrebbero avere difficoltà con i gesti. Per supportare questi utenti, valuta la possibilità di fornire un modo alternativo per regolare o reimpostare la scala che non si basi sui gesti.
  • Crea per tutte le scale: testa la tua UI sia con la scalabilità in-app sia con le impostazioni di visualizzazione o dei caratteri a livello di sistema. Verifica che i layout dell'app si adattino correttamente senza interrompere, sovrapporre o nascondere i contenuti. Scopri di più su come creare layout adattivi.