In React Native la dimensione del carattere non dovrebbe essere considerata soltanto come un valore fisso in pixel: l’app deve tenere conto della densità dei pixel, delle dimensioni dello schermo e delle preferenze di accessibilità impostate dall’utente. Per rispettare la dimensione del testo di sistema, il metodo principale è lasciare attivo il ridimensionamento di Text tramite allowFontScaling e, quando necessario, controllarlo con maxFontSizeMultiplier.
Per ottenere una dimensione adattiva in base allo schermo è possibile combinare Dimensions, PixelRatio.get(), PixelRatio.getFontScale() e una funzione di normalizzazione. È però importante distinguere tra scala dello schermo e scala del carattere: la prima serve ad adattare layout e misure fisiche, mentre la seconda riflette direttamente le preferenze tipografiche dell’utente.
Perché la dimensione del carattere è importante
Il ridimensionamento dei caratteri è una delle funzionalità di accessibilità più importanti nelle applicazioni mobili. Gli utenti con problemi di vista fanno affidamento sulle impostazioni di sistema per rendere leggibile il testo sui propri dispositivi.
Secondo l’Organizzazione Mondiale della Sanità, circa 2,2 miliardi di persone nel mondo presentano una qualche forma di disabilità visiva. Molti di questi utenti modificano la dimensione del carattere del dispositivo per leggere comodamente il testo.
Quando un’applicazione ignora queste preferenze, esclude di fatto una parte significativa dei suoi potenziali utenti. Rispettare il ridimensionamento del carattere di sistema è inoltre un aspetto fondamentale della conformità ai requisiti di accessibilità.
Il ridimensionamento dei caratteri non è utile soltanto alle persone con disabilità. Anche chi utilizza il dispositivo sotto la luce diretta del sole, gli utenti più anziani o chiunque preferisca un testo più grande può trarre beneficio da un’implementazione corretta.
Pixel, dp e dimensioni logiche in React Native
React Native non lavora normalmente con i pixel fisici del pannello, ma con dimensioni logiche indipendenti dalla densità del dispositivo. La densità dei pixel varia tra i dispositivi e può modificare il risultato visivo di una stessa dimensione dichiarata.
PixelRatio è un modulo di utilità che permette di convertire i valori tra l’unità indipendente dal dispositivo e il pixel fisico dello schermo. Può essere utile per gestire densità e risoluzioni differenti.
Dimensions permette di recuperare larghezza e altezza della finestra del dispositivo. Questi valori possono essere utilizzati come riferimento per adattare il layout e gli stili in base alle dimensioni e alle proporzioni dello schermo.
Un carattere con dimensione 40 può apparire drasticamente diverso su un iPhone XS Max rispetto a un iPhone 4. Per normalizzare dimensione del carattere, padding e margini tra dispositivi differenti occorre considerare principalmente due fattori: densità dei pixel e dimensioni dello schermo.
API PixelRatio: densità e scala del carattere
L’API PixelRatio fornisce diversi metodi utili:
- PixelRatio.get() restituisce la densità dei pixel del dispositivo;
- PixelRatio.getFontScale() restituisce il fattore di scala del carattere impostato dall’utente;
- PixelRatio.getPixelSizeForLayoutSize(100) converte una dimensione di layout in una dimensione espressa in pixel;
- PixelRatio.roundToNearestPixel(14.4) arrotonda un valore al pixel più vicino per ottenere un rendering più nitido.
PixelRatio.get() corrisponde ai valori @1x, @2x e @3x utilizzati su iOS e alle densità mdpi, hdpi, xhdpi e simili utilizzate su Android. Questo valore può essere impiegato come primo riferimento per modificare le dimensioni.
PixelRatio.getFontScale() ha invece uno scopo diverso: restituisce il fattore di ridimensionamento tipografico scelto nelle impostazioni del sistema. Il valore predefinito è 1.0.
Su iOS il valore può variare da 0.823 fino a 3.0 o oltre con le dimensioni di accessibilità più grandi, mentre su Android può variare da 0.85 a 1.3, in base al dispositivo.
import { PixelRatio } from 'react-native';// Get the current font scaleconst fontScale = PixelRatio.getFontScale();console.log('Current font scale:', fontScale);// Default is 1.0Come gli utenti modificano la dimensione del carattere
iOS e Android mettono a disposizione impostazioni di sistema per la dimensione del testo.
- iOS: Impostazioni > Schermo e luminosità > Dimensione testo, oppure Impostazioni > Accessibilità > Schermo e dimensioni testo > Testo più grande;
- Android: Impostazioni > Display > Dimensione carattere, con un percorso che può variare in base al produttore.
Queste impostazioni producono un fattore di scala che le applicazioni possono utilizzare per aumentare o ridurre proporzionalmente il testo.
Su Android è possibile cercare e selezionare Dimensioni carattere, quindi spostare il cursore verso sinistra o verso destra. È inoltre possibile modificare separatamente le Dimensioni di visualizzazione.
Nel sistema Android è disponibile un’anteprima della modifica della dimensione del carattere e della visualizzazione dopo aver regolato ciascun cursore. Se il risultato non è soddisfacente, è possibile toccare Ripristina impostazioni per riportare le preferenze ai valori originali.
Quando la dimensione del carattere viene modificata dalle impostazioni, il riquadro Dimensioni carattere può essere aggiunto automaticamente alle Impostazioni rapide. In alternativa, è possibile aggiungere prima questo riquadro alle Impostazioni rapide.
Le Impostazioni rapide consentono di regolare la dimensione del carattere fino al 200%. L’impostazione della dimensione del carattere, tuttavia, non viene applicata ad alcune applicazioni.
La proprietà allowFontScaling
Il componente Text di React Native include la proprietà allowFontScaling, che controlla se il testo deve rispettare le impostazioni del carattere del sistema.
import { Text, StyleSheet } from 'react-native';function TextExamples() { return ( <> {/* This text WILL scale with system settings (default) */} <Text style={styles.body}> This text respects user preferences </Text> {/* This text will NOT scale */} <Text style={styles.body} allowFontScaling={false}> This text stays fixed at 16pt </Text> > );}const styles = StyleSheet.create({ body: { fontSize: 16, },});Il comportamento predefinito consente il ridimensionamento del testo. In generale, è preferibile non disattivarlo, perché l’utente deve poter applicare all’applicazione le proprie preferenze di leggibilità.
La proprietà allowFontScaling={false} mantiene invece il carattere a una dimensione fissa. Questa scelta deve essere limitata ai casi in cui il ridimensionamento comprometterebbe il funzionamento o la leggibilità dell’elemento.
Quando disattivare il ridimensionamento
Esistono casi legittimi in cui disattivare il ridimensionamento può avere senso. Un esempio è rappresentato dai badge di notifica, che spesso devono rimanere all’interno di spazi molto ridotti.
import { Text, View, StyleSheet } from 'react-native';function IconBadge({ count }: { count: number }) { return ( <View style={styles.badge}> <Text style={styles.badgeText} allowFontScaling={false}> {count > 99 ? '99+' : count} </Text> </View> );}function TimerDisplay({ time }: { time: string }) { return ( <View style={styles.timerContainer}> <Text style={styles.timerText} allowFontScaling={false}> {time} </Text> </View> );}const styles = StyleSheet.create({ badge: { width: 20, height: 20, borderRadius: 10, backgroundColor: 'red', justifyContent: 'center', alignItems: 'center', }, badgeText: { color: 'white', fontSize: 10, fontWeight: 'bold', }, timerContainer: { backgroundColor: '#000', padding: 16, }, timerText: { fontFamily: 'monospace', fontSize: 48, color: '#0f0', },});I display a larghezza fissa, come i timer, possono rompersi quando il testo viene ridimensionato. Anche in questo caso la disattivazione dovrebbe essere una decisione mirata, non il comportamento generale dell’applicazione.
Limitare la dimensione con maxFontSizeMultiplier
La proprietà maxFontSizeMultiplier limita il livello massimo di ingrandimento del testo e aiuta a prevenire la rottura del layout quando l’utente utilizza dimensioni di accessibilità estreme.
import { Text, StyleSheet } from 'react-native';function ControlledScalingText() { return ( <> {/* Scale up to 1.5x the base size maximum */} <Text style={styles.heading} maxFontSizeMultiplier={1.5}> This heading scales up to 1.5x </Text> {/* Scale up to 2x for better accessibility */} <Text style={styles.body} maxFontSizeMultiplier={2}> Body text can scale more for readability </Text> {/* Unlimited scaling (default behavior) */} <Text style={styles.body}> This text has no scaling limit </Text> > );}const styles = StyleSheet.create({ heading: { fontSize: 24, fontWeight: 'bold', }, body: { fontSize: 16, },});Elementi diversi dell’interfaccia possono richiedere limiti differenti. I titoli grandi hanno spesso meno spazio disponibile, mentre il testo del corpo dovrebbe poter crescere maggiormente per favorire la leggibilità.
interface TypographyScale { heading: number; subheading: number; body: number; caption: number; button: number;}const MAX_SCALE: TypographyScale = { heading: 1.3, subheading: 1.4, body: 2.0, caption: 1.8, button: 1.3,};function Card({ title, subtitle, body,}: { title: string; subtitle: string; body: string;}) { return ( <View style={styles.card}> <Text style={styles.title} maxFontSizeMultiplier={MAX_SCALE.heading} > {title} </Text> <Text style={styles.subtitle} maxFontSizeMultiplier={MAX_SCALE.subheading} > {subtitle} </Text> <Text style={styles.body} maxFontSizeMultiplier={MAX_SCALE.body} > {body} </Text> </View> );}const styles = StyleSheet.create({ card: { padding: 16, backgroundColor: '#fff', borderRadius: 8, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 4, }, subtitle: { fontSize: 18, color: '#666', marginBottom: 12, }, body: { fontSize: 16, lineHeight: 24, },});Rilevare le modifiche della scala durante l’esecuzione
L’utente può modificare la dimensione del carattere mentre l’applicazione è in esecuzione. L’API Dimensions può aiutare a rilevare queste modifiche.
import { useEffect, useState } from 'react';import { Dimensions, PixelRatio } from 'react-native';function useFontScale(): number { const [fontScale, setFontScale] = useState( PixelRatio.getFontScale() ); useEffect(() => { const subscription = Dimensions.addEventListener( 'change', () => { setFontScale(PixelRatio.getFontScale()); } ); return () => subscription.remove(); }, []); return fontScale;}function MyComponent() { const fontScale = useFontScale(); return ( <Text>Current font scale: {fontScale}</Text> );}Questo hook legge il valore iniziale con PixelRatio.getFontScale() e aggiorna lo stato quando cambia la configurazione delle dimensioni. Il componente può quindi reagire alla nuova scala senza dover essere riavviato manualmente.
Creare una funzione per il fontSize adattivo
Una funzione di utilità può applicare il fattore di scala del sistema a una dimensione di base. È possibile usare un limite minimo e massimo per evitare valori estremi.
import { PixelRatio } from 'react-native';interface ScaledFontOptions { minScale?: number; maxScale?: number;}function getScaledFontSize( baseFontSize: number, options: ScaledFontOptions = {}): number { const { minScale = 0.8, maxScale = 1.5, } = options; const fontScale = PixelRatio.getFontScale(); const clampedScale = Math.min( Math.max(fontScale, minScale), maxScale ); return Math.round(baseFontSize * clampedScale);}const scaledBodySize = getScaledFontSize(16);const scaledHeaderSize = getScaledFontSize(24, { maxScale: 1.3,});Questa soluzione calcola esplicitamente il valore ridimensionato. Se il testo viene renderizzato da Text con allowFontScaling attivo, è necessario evitare di applicare due volte la stessa scala.
Funzione di normalizzazione basata sulla larghezza dello schermo
Un’applicazione React Native responsive è progettata per funzionare e visualizzarsi correttamente su schermi e risoluzioni differenti. Può adattarsi a smartphone, tablet e desktop mantenendo un’esperienza utente coerente.
import { Dimensions, Platform, PixelRatio } from 'react-native';const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT,} = Dimensions.get('window');Questo codice estrae le proprietà width e height dall’oggetto restituito da Dimensions.get('window') e le assegna alle variabili SCREEN_WIDTH e SCREEN_HEIGHT.
Le variabili possono essere utilizzate in altre parti del codice per modificare layout e stili in base alle dimensioni e alle proporzioni dello schermo.
const scale = SCREEN_WIDTH / 375;
Questo codice calcola un fattore di scala sulla base della larghezza dello schermo. Il valore di riferimento 375 viene utilizzato come base per calcolare il fattore di ridimensionamento.
Dividendo la larghezza del dispositivo per 375, il fattore risultante può essere usato per rendere proporzionali le dimensioni del layout allo schermo. Per esempio, se la larghezza di un componente è impostata a 100, moltiplicarla per il fattore scale ne adatta la misura alla larghezza del dispositivo.
const scaleVertical = SCREEN_HEIGHT / 812;
Questo codice calcola un fattore di scala sulla base dell’altezza dello schermo. Il valore di riferimento 812 viene utilizzato come base per il ridimensionamento verticale.
Il fattore scaleVertical può essere utilizzato per adattare altezza, spaziatura verticale e dimensioni degli elementi in base all’altezza del dispositivo.
Normalizzazione orizzontale e verticale
export function actuatedNormalize(size) { const newSize = size * scale; if (Platform.OS === 'ios') { return Math.round( PixelRatio.roundToNearestPixel(newSize) ); } else { return Math.round( PixelRatio.roundToNearestPixel(newSize) ) - 1; }}La funzione actuatedNormalize riceve una dimensione e la moltiplica per il fattore di scala calcolato sulla larghezza dello schermo. Il risultato viene arrotondato al pixel più vicino tramite PixelRatio.roundToNearestPixel().
Su Android viene sottratto un pixel aggiuntivo dopo l’arrotondamento. Questa regolazione tiene conto delle differenze di rendering dei caratteri tra Android e iOS.
export function actuatedNormalizeVertical(size) { const newSize = size * scaleVertical; if (Platform.OS === 'ios') { return Math.round( PixelRatio.roundToNearestPixel(newSize) ); } else { return Math.round( PixelRatio.roundToNearestPixel(newSize) ) - 1; }}actuatedNormalizeVertical funziona in modo simile, ma utilizza il fattore di scala verticale calcolato sull’altezza dello schermo. Il valore restituito può essere utilizzato per adattare dimensioni verticali, margini, padding e altezze.
Distinguere smartphone e tablet
export function isTab() { if (SCREEN_WIDTH > 550) { return true; } else { return false; }}La funzione isTab() restituisce un valore booleano in base alla larghezza dello schermo. Se la larghezza supera 550, restituisce true; altrimenti restituisce false.
Questa funzione può essere utilizzata per determinare se lo schermo è abbastanza ampio da mostrare un layout in stile tablet o una quantità maggiore di contenuti.
export function isScreenHeight770() { if (SCREEN_HEIGHT > 740 && SCREEN_HEIGHT < 760) { return true; } else { return false; }}La funzione isScreenHeight770() restituisce true quando l’altezza dello schermo è maggiore di 740 e minore di 760. In caso contrario restituisce false.
Utilizzare PixelScaling.js
Le funzioni possono essere raccolte in un file chiamato PixelScaling.js all’interno della cartella Constant.
import { Dimensions, Platform, PixelRatio } from 'react-native';const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT,} = Dimensions.get('window');const scale = SCREEN_WIDTH / 375;const scaleVertical = SCREEN_HEIGHT / 812;export function actuatedNormalize(size) { const newSize = size * scale; if (Platform.OS === 'ios') { return Math.round( PixelRatio.roundToNearestPixel(newSize) ); } else { return Math.round( PixelRatio.roundToNearestPixel(newSize) ) - 1; }}export function actuatedNormalizeVertical(size) { const newSize = size * scaleVertical; if (Platform.OS === 'ios') { return Math.round( PixelRatio.roundToNearestPixel(newSize) ); } else { return Math.round( PixelRatio.roundToNearestPixel(newSize) ) - 1; }}export function isTab() { if (SCREEN_WIDTH > 550) { return true; } else { return false; }}export function isScreenHeight770() { if (SCREEN_HEIGHT > 740 && SCREEN_HEIGHT < 760) { return true; } else { return false; }}Per utilizzare queste funzioni, è sufficiente importare il file nel componente o nel file principale dell’applicazione.
import { actuatedNormalize, actuatedNormalizeVertical, isTab,} from './constants/PixelScaling';Applicare la normalizzazione agli stili
actuatedNormalizeVertical può essere utilizzata per valori superiori e inferiori, come margine superiore, margine inferiore, padding superiore, padding inferiore, altezza e altri elementi verticali.
actuatedNormalize può essere utilizzata per valori laterali, come margine sinistro, margine destro, padding sinistro, padding destro, larghezza e dimensione del carattere.
const styles = StyleSheet.create({ normal: { marginTop: 17, marginBottom: 17, paddingTop: 17, paddingBottom: 17, marginLeft: 17, marginRight: 17, paddingLeft: 17, paddingRight: 17, fontSize: 17, width: 17, height: 17, }, ForTopBottom: { marginTop: actuatedNormalizeVertical(17), marginBottom: actuatedNormalizeVertical(17), paddingTop: actuatedNormalizeVertical(17), paddingBottom: actuatedNormalizeVertical(17), }, ForLeftRight: { marginLeft: actuatedNormalize(17), marginRight: actuatedNormalize(17), paddingLeft: actuatedNormalize(17), paddingRight: actuatedNormalize(17), }, ForFonts: { fontSize: actuatedNormalize(17), }, ForImage: { width: actuatedNormalize(17), height: actuatedNormalizeVertical(17), },});Per distinguere il layout tablet è possibile utilizzare un operatore ternario con isTab().
const styles = StyleSheet.create({ ForTab: { marginTop: isTab() ? actuatedNormalizeVertical(29) : actuatedNormalizeVertical(17), marginBottom: isTab() ? actuatedNormalizeVertical(29) : actuatedNormalizeVertical(17), marginLeft: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), marginRight: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), paddingTop: isTab() ? actuatedNormalizeVertical(29) : actuatedNormalizeVertical(17), paddingBottom: isTab() ? actuatedNormalizeVertical(29) : actuatedNormalizeVertical(17), paddingLeft: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), paddingRight: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), fontSize: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), width: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), height: isTab() ? actuatedNormalize(29) : actuatedNormalize(17), },});Normalizzare il carattere in base alla larghezza
Una funzione di normalizzazione può essere collocata in un file di utilità come src/utilities/normalize.js e importata dove necessario.
import { PixelRatio, Dimensions } from 'react-native';const ratio = PixelRatio.get();const normalize = (size) => { const { width, height } = Dimensions.get('window'); return size;};export default normalize;La larghezza e l’altezza vengono lette ogni volta che la funzione viene chiamata perché possono cambiare con l’orientamento del dispositivo. Il costo di questa operazione è minimo.
Normalizzazione per densità e dimensioni
È possibile suddividere i dispositivi in categorie generali sulla base del rapporto di pixel.
import { PixelRatio, Dimensions } from 'react-native';const ratio = PixelRatio.get();const normalize = (size) => { const { width, height } = Dimensions.get('window'); if (ratio >= 2 && ratio < 3) { // scale } else if (ratio >= 3 && ratio < 3.5) { // scale } else if (ratio >= 3.5) { // scale } return size;};export default normalize;Il rapporto 1 può essere utilizzato come riferimento di base, mentre i caratteri vengono scalati in base a categorie comuni. In genere il rapporto è 1, 2 o 3, ma su Android può assumere anche valori intermedi, perciò è utile lavorare con intervalli.
Le dimensioni possono essere suddivise in tre categorie principali: larghezza del dispositivo inferiore a 360, altezza inferiore a 667 e altezza compresa tra 667 e 735. I dispositivi rimanenti rientrano nella categoria generale.
import { PixelRatio, Dimensions } from 'react-native';const ratio = PixelRatio.get();const normalize = (size) => { const { width, height } = Dimensions.get('window'); if (ratio >= 2 && ratio < 3) { if (width < 360) { // scale } else if (height < 667) { // scale } else if (height >= 667 && height <= 735) { // scale } // scale } else if (ratio >= 3 && ratio < 3.5) { if (width < 360) { // scale } else if (height < 667) { // scale } else if (height >= 667 && height <= 735) { // scale } // scale } else if (ratio >= 3.5) { if (width < 360) { // scale } else if (height < 667) { // scale } else if (height >= 667 && height <= 735) { // scale } // scale } return size;};export default normalize;Il fattore di scala deve essere regolato in base al progetto. In genere il rapporto di ridimensionamento è compreso intorno a più o meno 0.2, salvo il caso in cui si prendano in considerazione anche i tablet.
Codice di normalizzazione completo
import { PixelRatio, Dimensions } from 'react-native';const ratio = PixelRatio.get();const normalize = (size) => { const { width, height } = Dimensions.get('window'); if (ratio >= 2 && ratio < 3) { if (width < 360) { return size * 0.95; } else if (height < 667) { return size; } else if (height >= 667 && height <= 735) { return size * 1.15; } return size * 1.25; } else if (ratio >= 3 && ratio < 3.5) { if (width < 360) { return size; } else if (height < 667) { return size * 1.15; } else if (height >= 667 && height <= 735) { return size * 1.2; } return size * 1.27; } else if (ratio >= 3.5) { if (width < 360) { return size; } else if (height < 667) { return size * 1.2; } else if (height >= 667 && height <= 735) { return size * 1.25; } return size * 1.4; } return size;};export default normalize;Questo approccio modifica la dimensione in base alla densità dei pixel, alla larghezza e all’altezza della finestra. Deve essere verificato sui dispositivi realmente supportati dall’applicazione.
Dimensioni di base per una scala tipografica
Quando si definiscono le dimensioni dei caratteri in modo diretto, il risultato può variare da un dispositivo all’altro perché ogni schermo ha una densità differente. Per questo motivo è possibile definire una larghezza e un’altezza di riferimento.
const baseWidth = 375;const baseHeight = 667;const scale = SCREEN_WIDTH / 320;
La funzione seguente utilizza il fattore di scala per calcolare la dimensione del carattere.
import { Dimensions, Platform, PixelRatio } from 'react-native';const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT,} = Dimensions.get('window');const scale = SCREEN_WIDTH / 320;export function normalize(size) { const newSize = size * scale; if (Platform.OS === 'ios') { return Math.round( PixelRatio.roundToNearestPixel(newSize) ); } else { return Math.round( PixelRatio.roundToNearestPixel(newSize) ) - 2; }}La funzione normalize viene esportata e può essere utilizzata per calcolare la dimensione dei caratteri nell’applicazione.
const styles = { mini: { fontSize: normalize(12), }, small: { fontSize: normalize(15), }, medium: { fontSize: normalize(17), }, large: { fontSize: normalize(20), }, xlarge: { fontSize: normalize(24), },};È possibile importare la funzione e utilizzarla negli stili in cui si desidera una dimensione adattiva.
Ridimensionamento verticale e spaziatura
La dimensione del carattere non è l’unico valore che può richiedere un adattamento. Padding e margini possono essere ridimensionati in modo proporzionale, con un limite per evitare spaziature eccessive.
import { View, Text, StyleSheet, PixelRatio } from 'react-native';function useScaledSpacing() { const fontScale = PixelRatio.getFontScale(); const scaleFactor = Math.min(fontScale, 1.5); return { xs: 4 * scaleFactor, sm: 8 * scaleFactor, md: 16 * scaleFactor, lg: 24 * scaleFactor, xl: 32 * scaleFactor, };}function ResponsiveLayout() { const spacing = useScaledSpacing(); return ( <View style={[styles.container, { padding: spacing.md }]}> <Text style={styles.title}> Responsive Layout </Text> <View style={{ marginTop: spacing.sm }}> <Text style={styles.body}> Spacing scales with font size for visual harmony. </Text> </View> </View> );}const styles = StyleSheet.create({ container: { backgroundColor: '#fff', }, title: { fontSize: 20, fontWeight: 'bold', }, body: { fontSize: 16, },});Altezza flessibile dei contenitori
I contenitori con altezza fissa possono tagliare il testo quando la scala aumenta. È preferibile utilizzare un’altezza minima che possa espandersi in base al contenuto.
function FixedHeightCard({ title, description,}: { title: string; description: string;}) { return ( <View style={badStyles.card}> <Text style={badStyles.title}>{title}</Text> <Text style={badStyles.description}> {description} </Text> </View> );}const badStyles = StyleSheet.create({ card: { height: 100, padding: 16, backgroundColor: '#fff', }, title: { fontSize: 18, fontWeight: 'bold', }, description: { fontSize: 14, },});Una soluzione più robusta utilizza minHeight e consente al testo di andare a capo.
function FlexibleCard({ title, description,}: { title: string; description: string;}) { return ( <View style={goodStyles.card}> <Text style={goodStyles.title}>{title}</Text> <Text style={goodStyles.description}> {description} </Text> </View> );}const goodStyles = StyleSheet.create({ card: { minHeight: 100, padding: 16, backgroundColor: '#fff', }, title: { fontSize: 18, fontWeight: 'bold', marginBottom: 8, }, description: { fontSize: 14, flexShrink: 1, },});Gestire i layout orizzontali
I layout orizzontali possono richiedere una disposizione verticale quando il carattere è molto grande o lo schermo è stretto.
import { View, Text, StyleSheet, useWindowDimensions, PixelRatio,} from 'react-native';function AdaptiveButtonRow({ primaryLabel, secondaryLabel, onPrimary, onSecondary,}: { primaryLabel: string; secondaryLabel: string; onPrimary: () => void; onSecondary: () => void;}) { const { width } = useWindowDimensions(); const fontScale = PixelRatio.getFontScale(); const shouldStack = fontScale > 1.3 || width < 350; return ( <View style={[ styles.buttonRow, shouldStack && styles.buttonRowStacked, ]} > <Pressable style={[styles.button, styles.primaryButton]} onPress={onPrimary} > <Text style={styles.buttonText}> {primaryLabel} </Text> </Pressable> <Pressable style={[ styles.button, styles.secondaryButton, shouldStack && styles.stackedButton, ]} onPress={onSecondary} > <Text style={[styles.buttonText, styles.secondaryText]} > {secondaryLabel} </Text> </Pressable> </View> );}const styles = StyleSheet.create({ buttonRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 12, }, buttonRowStacked: { flexDirection: 'column', }, button: { flex: 1, paddingVertical: 12, paddingHorizontal: 24, borderRadius: 8, alignItems: 'center', }, stackedButton: { marginTop: 8, }, primaryButton: { backgroundColor: '#007AFF', }, secondaryButton: { backgroundColor: 'transparent', borderWidth: 1, borderColor: '#007AFF', }, buttonText: { fontSize: 16, fontWeight: '600', color: '#fff', }, secondaryText: { color: '#007AFF', },});In questo esempio il layout passa dalla riga alla colonna quando il fattore di scala supera 1.3 oppure quando la larghezza della finestra è inferiore a 350.
Altezza della riga e dimensione del carattere
L’altezza della riga dovrebbe crescere in modo proporzionale alla dimensione del carattere. Se il testo aumenta ma lineHeight rimane fisso, le righe possono sovrapporsi o risultare troppo ravvicinate.
import { PixelRatio, StyleSheet } from 'react-native';interface TypographyStyle { fontSize: number; lineHeight: number; letterSpacing?: number;}function createScaledTypography( baseFontSize: number, lineHeightMultiplier: number = 1.5): TypographyStyle { const fontScale = PixelRatio.getFontScale(); const scaledSize = baseFontSize * fontScale; return { fontSize: baseFontSize, lineHeight: Math.round( baseFontSize * lineHeightMultiplier * fontScale ), };}Nel codice precedente la variabile scaledSize viene calcolata, mentre React Native gestisce automaticamente la scala del valore fontSize quando allowFontScaling è attivo.
function useResponsiveTypography() { const fontScale = PixelRatio.getFontScale(); return StyleSheet.create({ heading: { fontSize: 24, lineHeight: Math.round(32 * fontScale), fontWeight: 'bold', }, body: { fontSize: 16, lineHeight: Math.round(24 * fontScale), }, small: { fontSize: 12, lineHeight: Math.round(18 * fontScale), }, });}Font personalizzati
I font personalizzati vengono ridimensionati come i font di sistema quando allowFontScaling è attivo. È comunque necessario definire correttamente le famiglie tipografiche utilizzate da iOS e Android.
import { Text, StyleSheet, Platform } from 'react-native';const fonts = { regular: Platform.select({ ios: 'Roboto-Regular', android: 'Roboto-Regular', }), bold: Platform.select({ ios: 'Roboto-Bold', android: 'Roboto-Bold', }), light: Platform.select({ ios: 'Roboto-Light', android: 'Roboto-Light', }),};const typography = StyleSheet.create({ h1: { fontFamily: fonts.bold, fontSize: 32, lineHeight: 40, letterSpacing: -0.5, }, h2: { fontFamily: fonts.bold, fontSize: 24, lineHeight: 32, }, body: { fontFamily: fonts.regular, fontSize: 16, lineHeight: 24, }, caption: { fontFamily: fonts.light, fontSize: 12, lineHeight: 16, },});function CustomFontText() { return ( <> <Text style={typography.h1}> Custom Font Heading </Text> <Text style={typography.body}> Custom fonts scale just like system fonts when allowFontScaling is true. </Text> > );}Il font personalizzato non elimina la necessità di progettare contenitori flessibili, altezze di riga adeguate e limiti di scala coerenti con il ruolo dell’elemento.
Impostare un comportamento predefinito
È possibile centralizzare il comportamento di allowFontScaling creando un componente Text personalizzato. L’utilizzo di Text.defaultProps è invece un approccio in fase di dismissione.
import { Text as RNText, TextProps,} from 'react-native';interface CustomTextProps extends TextProps { allowFontScaling?: boolean;}function Text({ allowFontScaling = true, ...props}: CustomTextProps) { return ( <RNText allowFontScaling={allowFontScaling} {...props} /> );}export default Text;Con questo componente il ridimensionamento rimane attivo per impostazione predefinita, ma può essere disabilitato esplicitamente nei casi in cui sia realmente necessario.
Applicare automaticamente la normalizzazione agli stili
Quando si desidera normalizzare più proprietà contemporaneamente, è possibile creare una funzione che individua determinate chiavi e applica automaticamente la normalizzazione.
export const create = ( styles, targetProperties = [ 'fontSize', 'margin', 'marginHorizontal', 'marginVertical', 'padding', 'paddingVertical', 'paddingHorizontal', 'height', ]) => { const normalizedStyles = {}; Object.keys(styles).forEach((key) => { normalizedStyles[key] = {}; Object.keys(styles[key]).forEach((property) => { if (targetProperties.includes(property)) { normalizedStyles[key][property] = normalize(styles[key][property]); } else { normalizedStyles[key][property] = styles[key][property]; } }); }); return StyleSheet.create(normalizedStyles);};import { create } from './utilities/normalize.js';const styles = create({ text: { color: '#d4895e', fontWeight: 'bold', fontSize: 40, marginHorizontal: 20, },});Questo metodo consente di normalizzare automaticamente proprietà come fontSize, margin, padding e height, lasciando inalterate le altre proprietà.
Troncamento del testo e overflow
Quando lo spazio è limitato, il testo deve essere gestito in modo esplicito. È possibile utilizzare una modalità di troncamento per evitare che una stringa troppo lunga rompa il layout.
import { Text } from 'react-native';function TruncatedText({ title }: { title: string }) { return ( <Text numberOfLines={1} ellipsizeMode="tail"> {title} </Text> );}Il troncamento deve essere utilizzato con cautela nei contenuti informativi, perché una dimensione del carattere maggiore può rendere invisibile una parte importante del testo. Quando possibile, è preferibile permettere al testo di andare a capo e fare crescere il contenitore.
Verificare la leggibilità su dispositivi differenti
La progettazione dovrebbe considerare smartphone di dimensioni diverse, tablet e dispositivi Android con rapporti di pixel differenti. Un’applicazione sviluppata esclusivamente su un iPhone X o su un unico simulatore può presentare problemi su schermi più piccoli o su dispositivi meno recenti.
È importante verificare il comportamento con caratteri piccoli e grandi, con orientamento verticale e orizzontale, con larghezze ridotte e con le dimensioni di accessibilità più elevate.
La funzione di normalizzazione dovrebbe essere considerata un punto di partenza e non una sostituzione dei test visivi. I fattori di scala devono essere regolati in base alle esigenze del progetto e ai dispositivi supportati.
Il ridimensionamento basato sulla larghezza dello schermo può migliorare la coerenza visiva, ma non deve impedire all’utente di ingrandire il testo tramite le impostazioni di accessibilità.
Altre impostazioni di accessibilità Android
Su Android è possibile modificare la dimensione del carattere e la dimensione della visualizzazione dalle impostazioni del dispositivo. Alcuni passaggi sono validi soltanto per Android 13 e versioni successive.
Per migliorare la visibilità è possibile attivare il carattere in grassetto. L’impostazione non è disponibile per alcune app.
Per utilizzare il testo a contrasto, sul dispositivo deve essere installato Android 16 o una versione successiva. Sui dispositivi con Android 15 e versioni precedenti è possibile utilizzare un testo ad alto contrasto.
Le impostazioni di correzione del colore e scala di grigi consentono al dispositivo di compensare alcuni problemi di daltonismo. L’inversione dei colori inverte i colori sullo schermo e si applica a tutti i contenuti multimediali inclusi.
Android 10 e versioni successive permettono di utilizzare l’inversione dei colori quando un’app non supporta il tema scuro. Android 9 e versioni precedenti consentono di utilizzare soltanto una di queste impostazioni alla volta.
Le animazioni possono essere disattivate per facilitare la concentrazione. È inoltre possibile ingrandire il cursore del mouse e attenuare lo schermo oltre la luminosità minima del telefono per facilitare la lettura.
Scelta dell’approccio corretto
| Esigenza | Approccio |
|---|---|
| Rispettare le preferenze dell’utente | Utilizzare allowFontScaling attivo |
| Limitare l’ingrandimento di un elemento | Utilizzare maxFontSizeMultiplier |
| Adattare il testo alla larghezza del dispositivo | Utilizzare Dimensions.get('window') e una funzione di normalizzazione |
| Gestire la densità dello schermo | Utilizzare PixelRatio.get() |
| Arrotondare la dimensione al pixel più vicino | Utilizzare PixelRatio.roundToNearestPixel() |
| Reagire alle impostazioni modificate durante l’esecuzione | Utilizzare un hook basato su Dimensions.addEventListener() |
| Adattare l’altezza delle righe | Calcolare lineHeight proporzionalmente alla scala |
| Prevenire il taglio del testo | Preferire minHeight ad altezza fissa e consentire il ritorno a capo |
| Gestire spazi ridotti | Riorganizzare il layout, per esempio da riga a colonna |
La soluzione più equilibrata combina il ridimensionamento del carattere di sistema con un layout flessibile. La normalizzazione basata su larghezza, altezza o densità può essere utile per mantenere proporzioni coerenti, ma non dovrebbe sostituire il supporto alle impostazioni di accessibilità.
