Intervalli

Prova il metodo Scrivi
Jetpack Compose è il toolkit consigliato per la UI per Android. Scopri come utilizzare il testo in Compose.

Gli intervalli sono potenti oggetti di markup che puoi utilizzare per definire lo stile del testo a livello di carattere o di paragrafo. Collegare sezioni a oggetti di testo, per modificare il testo in vari modi, ad esempio aggiungendo colore, rendendo il testo cliccabile, scalare la dimensione del testo e disegnarlo in modo personalizzato. Gli intervalli possono anche modifica le proprietà di TextPaint, disegna su un Canvas e modifica il layout del testo.

Android offre diversi tipi di intervalli che coprono una varietà di testi comuni modelli di stile. Puoi anche creare intervalli personalizzati per applicare stili personalizzati.

Creare e applicare un intervallo

Per creare un intervallo, puoi utilizzare una delle classi elencate nella seguente tabella. Le classi variano a seconda che il testo sia mutabile o meno è modificabile e quale struttura di dati sottostante contiene i dati degli intervalli.

Classe Testo modificabile Markup modificabile Struttura dei dati
SpannedString No No Matrice lineare
SpannableString No Matrice lineare
SpannableStringBuilder Albero degli intervalli

Tutti e tre i corsi estendono la Spanned a riga di comando. SpannableString e SpannableStringBuilder estendono anche Spannable.

Per decidere quale utilizzare:

  • Se non modifichi il testo o il markup dopo la creazione, utilizza SpannedString.
  • Se devi collegare un numero ridotto di sezioni a un singolo oggetto di testo e il testo è di sola lettura, usa SpannableString.
  • Se devi modificare il testo dopo la creazione e devi allegare intervalli a il testo, usa SpannableStringBuilder.
  • Se è necessario collegare un numero elevato di intervalli a un oggetto di testo, per stabilire se il testo è di sola lettura, usa SpannableStringBuilder.

Per applicare un intervallo, chiama setSpan(Object _what_, int _start_, int _end_, int _flags_) su un oggetto Spannable. Il parametro what si riferisce all'estensione che stai applicabile al testo, mentre i parametri start e end indicano la parte del testo a cui applichi l'intervallo.

Se inserisci del testo all'interno dei confini di una sezione, questa si espande automaticamente in il testo inserito. Quando inserisci il testo nello intervallo confini, ovvero in corrispondenza degli indici start o end, i flag determina se l'intervallo si espande per includere il testo inserito. Utilizza le funzionalità di il Spannable.SPAN_EXCLUSIVE_INCLUSIVE per includere il testo inserito e utilizzare Spannable.SPAN_EXCLUSIVE_EXCLUSIVE per escludere il testo inserito.

L'esempio seguente mostra come collegare un ForegroundColorSpan a un stringa:

Kotlin

val spannable = SpannableStringBuilder("Text is spantastic!")
spannable.setSpan(
    ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)

Java

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
Un'immagine che mostra un testo grigio, parzialmente rosso.
. Figura 1. Testo con stile ForegroundColorSpan.

Poiché l'intervallo viene impostato utilizzando Spannable.SPAN_EXCLUSIVE_INCLUSIVE, l'intervallo si espande in modo da includere il testo inserito ai confini dell'intervallo, come mostrato in nell'esempio seguente:

Kotlin

val spannable = SpannableStringBuilder("Text is spantastic!")
spannable.setSpan(
    ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
spannable.insert(12, "(& fon)")

Java

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
spannable.insert(12, "(& fon)");
Un'immagine che mostra in che modo l'intervallo include più testo quando viene utilizzato SPAN_EXCLUSIVE_INCLUSIVE.
. Figura 2. L'intervallo si espande per includere testo aggiuntivo quando si utilizza Spannable.SPAN_EXCLUSIVE_INCLUSIVE.

Puoi allegare più sezioni allo stesso testo. L'esempio seguente mostra come per creare testo in grassetto e rosso:

Kotlin

val spannable = SpannableString("Text is spantastic!")
spannable.setSpan(ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(
    StyleSpan(Typeface.BOLD),
    8,
    spannable.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

Java

SpannableString spannable = new SpannableString("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, 12,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
spannable.setSpan(
    new StyleSpan(Typeface.BOLD),
    8, spannable.length(),
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
Un'immagine che mostra un testo con più sezioni: "ForegroundColorSpan(Color.RED)" e "StyleSpan(BOLD)"
. Figura 3. Testo con più sezioni: ForegroundColorSpan(Color.RED) e StyleSpan(BOLD).

Tipi di intervallo Android

Android offre oltre 20 tipi di intervalli android.text.style. Android classifica gli intervalli in due modi principali:

  • In che modo l'intervallo influisce sul testo: un intervallo può influire sull'aspetto del testo o sul testo metriche di valutazione.
  • Ambito di intervallo: alcune sezioni possono essere applicate a singoli caratteri, mentre altre deve essere applicata a un intero paragrafo.
di Gemini Advanced.
Un'immagine che mostra diverse categorie di intervalli
Figura 4. Categorie di intervalli Android.

Nelle sezioni seguenti vengono descritte queste categorie in modo più dettagliato.

Intervalli che influiscono sull'aspetto del testo

Alcuni intervalli che si applicano a livello di carattere influiscono sull'aspetto del testo, ad esempio: cambiare il colore del testo o dello sfondo e aggiungere sottolineature o barrature. Questi estendono CharacterStyle.

L'esempio di codice seguente mostra come applicare un UnderlineSpan per sottolineare il testo:

Kotlin

val string = SpannableString("Text with underline span")
string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

SpannableString string = new SpannableString("Text with underline span");
string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Un'immagine che mostra come sottolineare il testo utilizzando un "underlineSpan"
. Figura 5. Testo sottolineato con un UnderlineSpan.

Gli intervalli che influiscono solo sull'aspetto del testo attivano un nuovo disegno del testo senza attivando un ricalcolo del layout. Questi intervalli implementano UpdateAppearance ed estendi CharacterStyle. Le sottoclassi CharacterStyle definiscono come disegnare il testo fornendo accesso a aggiorna TextPaint.

Intervalli che influiscono sulle metriche di testo

Altri intervalli che si applicano a livello di carattere influiscono sulle metriche del testo, come le righe l'altezza e le dimensioni del testo. Questi intervalli estendono MetricAffectingSpan .

Il codice di esempio riportato di seguito crea un RelativeSizeSpan che aumenta la dimensione del testo del 50%:

Kotlin

val string = SpannableString("Text with relative size span")
string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

SpannableString string = new SpannableString("Text with relative size span");
string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Un'immagine che mostra l'utilizzo di relativeSizeSpan
. Figura 6. Testo ingrandito con un RelativeSizeSpan.

L'applicazione di un intervallo che influisce sulle metriche di testo fa sì che un oggetto "Osservazione" misurare nuovamente il testo per verificare che il layout e il rendering siano corretti, ad esempio modificando le dimensioni del testo potrebbero far apparire le parole su righe diverse. Applicazione della versione precedente attiva una nuova misurazione, un ricalcolo del layout del testo e un il testo.

Gli intervalli che influiscono sulle metriche del testo estendono la classe MetricAffectingSpan, un una classe astratta che consente alle sottoclassi di definire in che modo l'intervallo influisce sulla misurazione del testo fornendo l'accesso al TextPaint. Poiché MetricAffectingSpan si estende CharacterSpan, le sottoclassi influiscono sull'aspetto del testo in corrispondenza del carattere livello.

Intervalli che interessano i paragrafi

Una sezione può influire anche sul testo a livello di paragrafo, ad esempio modificando l'allineamento o il margine di un blocco di testo. Intervalli che interessano interi paragrafi implementare ParagraphStyle. A utilizzi queste sezioni, le alleghi all'intero paragrafo, escludendo la fine una nuova riga. Se provi ad applicare un'estensione di paragrafo a qualcosa di diverso un intero paragrafo, Android non applica affatto l'intervallo.

La Figura 8 mostra in che modo Android separa i paragrafi nel testo.

. Figura 7. In Android, i paragrafi terminano con nuova riga (\n).

Il codice di esempio riportato di seguito applica un QuoteSpan a un paragrafo. Tieni presente che se colleghi l'intervallo a qualsiasi posizione diversa dall'inizio o dalla fine di un , Android non applica lo stile.

Kotlin

spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Un'immagine che mostra un esempio di QuoteSpan
. Figura 8. QuoteSpan applicata a un paragrafo.

Creazione di intervalli personalizzati

Se hai bisogno di più funzionalità rispetto a quelle fornite nella versione attuale puoi implementare un intervallo personalizzato. Quando implementi il tuo intervallo, decidi se l'intervallo influisce sul testo a livello di carattere o di paragrafo. ma anche se influiscono sul layout o sull'aspetto del testo. Questo ti aiuta determinare quali classi base puoi estendere e quali interfacce potresti aver bisogno da implementare. Utilizza la seguente tabella come riferimento:

Scenario Classe o interfaccia
L'intervallo influisce sul testo a livello di carattere. CharacterStyle
L'intervallo influisce sull'aspetto del testo. UpdateAppearance
L'intervallo influisce sulle metriche del testo. UpdateLayout
L'intervallo influisce sul testo a livello di paragrafo. ParagraphStyle

Ad esempio, se devi implementare un intervallo personalizzato che modifichi le dimensioni del testo e colore, estendi RelativeSizeSpan. Tramite l'ereditarietà, RelativeSizeSpan estende CharacterStyle e implementa le due interfacce Update. Dal momento che offre già callback per updateDrawState e updateMeasureState, puoi ignorare questi callback per implementare il comportamento personalizzato. La il seguente codice crea un intervallo personalizzato che si estende RelativeSizeSpan esegue l'override del callback updateDrawState per impostare il colore di TextPaint:

Kotlin

class RelativeSizeColorSpan(
    size: Float,
    @ColorInt private val color: Int
) : RelativeSizeSpan(size) {
    override fun updateDrawState(textPaint: TextPaint) {
        super.updateDrawState(textPaint)
        textPaint.color = color
    }
}

Java

public class RelativeSizeColorSpan extends RelativeSizeSpan {
    private int color;
    public RelativeSizeColorSpan(float spanSize, int spanColor) {
        super(spanSize);
        color = spanColor;
    }
    @Override
    public void updateDrawState(TextPaint textPaint) {
        super.updateDrawState(textPaint);
        textPaint.setColor(color);
    }
}

Questo esempio illustra come creare un intervallo personalizzato. Puoi ottenere lo stesso applicando RelativeSizeSpan e ForegroundColorSpan al testo.

Utilizzo intervallo di test

L'interfaccia Spanned consente di impostare e recuperare intervalli da testo. Durante il test, implementa una JUnit Android test per verificare che vengano aggiunti gli intervalli corretti nelle posizioni corrette. Esempio di Stile di testo app contiene un intervallo che applica il markup agli elenchi puntati allegando BulletPointSpan al testo. Il seguente esempio di codice mostra come eseguire il test se gli elenchi puntati vengono visualizzati come previsto:

Kotlin

@Test fun textWithBulletPoints() {
   val result = builder.markdownToSpans("Points\n* one\n+ two")

   // Check whether the markup tags are removed.
   assertEquals("Points\none\ntwo", result.toString())

   // Get all the spans attached to the SpannedString.
   val spans = result.getSpans<Any>(0, result.length, Any::class.java)

   // Check whether the correct number of spans are created.
   assertEquals(2, spans.size.toLong())

   // Check whether the spans are instances of BulletPointSpan.
   val bulletSpan1 = spans[0] as BulletPointSpan
   val bulletSpan2 = spans[1] as BulletPointSpan

   // Check whether the start and end indices are the expected ones.
   assertEquals(7, result.getSpanStart(bulletSpan1).toLong())
   assertEquals(11, result.getSpanEnd(bulletSpan1).toLong())
   assertEquals(11, result.getSpanStart(bulletSpan2).toLong())
   assertEquals(14, result.getSpanEnd(bulletSpan2).toLong())
}

Java

@Test
public void textWithBulletPoints() {
    SpannedString result = builder.markdownToSpans("Points\n* one\n+ two");

    // Check whether the markup tags are removed.
    assertEquals("Points\none\ntwo", result.toString());

    // Get all the spans attached to the SpannedString.
    Object[] spans = result.getSpans(0, result.length(), Object.class);

    // Check whether the correct number of spans are created.
    assertEquals(2, spans.length);

    // Check whether the spans are instances of BulletPointSpan.
    BulletPointSpan bulletSpan1 = (BulletPointSpan) spans[0];
    BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1];

    // Check whether the start and end indices are the expected ones.
    assertEquals(7, result.getSpanStart(bulletSpan1));
    assertEquals(11, result.getSpanEnd(bulletSpan1));
    assertEquals(11, result.getSpanStart(bulletSpan2));
    assertEquals(14, result.getSpanEnd(bulletSpan2));
}

Per altri esempi di test, vedi MarkdownBuilderTest su GitHub.

Test di intervalli personalizzati

Durante il test degli intervalli, verifica che TextPaint contenga i valori previsti modifiche e che in Canvas vengano visualizzati gli elementi corretti. Per Ad esempio, considera un'implementazione di un intervallo personalizzato che anteponga un punto elenco a del testo. Il punto elenco ha una dimensione e un colore specificati e c'è uno spazio vuoto tra il margine sinistro dell'area disegnabile e il punto elenco.

Puoi testare il comportamento di questa classe implementando un test AndroidJUnit, verificando quanto segue:

  • Se applichi correttamente l'intervallo, viene fornito un punto elenco delle dimensioni specificate e compare il colore sulla tela e deve esserci uno spazio adeguato tra la parte sinistra a un margine e a un elenco puntato.
  • Se non applichi l'intervallo, non viene visualizzato alcun comportamento personalizzato.

Puoi vedere l'implementazione di questi test nel TextStyling di esempio su GitHub.

Puoi testare le interazioni con Canvas simulando la tela, trasmettendo quella oggetto drawLeadingMargin() e verificando che i metodi corretti siano richiamati parametri.

Puoi trovare altri campioni di test di intervallo in BulletPointSpanTest.

Best practice per l'utilizzo degli intervalli

Esistono diversi modi efficienti di memoria per impostare il testo in un TextView, a seconda in base alle tue esigenze.

Collegare o scollegare un intervallo senza modificare il testo sottostante

TextView.setText() contiene più sovraccarichi che gestiscono gli intervalli in modo diverso. Ad esempio, puoi imposta un oggetto di testo Spannable con il seguente codice:

Kotlin

textView.setText(spannableObject)

Java

textView.setText(spannableObject);

Quando chiami questo sovraccarico di setText(), TextView crea una copia dei tuoi Spannable come SpannedString e lo mantiene in memoria come CharSequence. Ciò significa che il testo e le sezioni sono immutabili, quindi quando devi aggiorna il testo o gli intervalli, crea un nuovo oggetto Spannable e richiama setText(), il che attiva anche una nuova misurazione e un nuovo disegno del layout.

Per indicare che gli intervalli devono essere modificabili, puoi utilizzare setText(CharSequence text, TextView.BufferType type), come mostrato nell'esempio seguente:

Kotlin

textView.setText(spannable, BufferType.SPANNABLE)
val spannableText = textView.text as Spannable
spannableText.setSpan(
     ForegroundColorSpan(color),
     8, spannableText.length,
     SPAN_INCLUSIVE_INCLUSIVE
)

Java

textView.setText(spannable, BufferType.SPANNABLE);
Spannable spannableText = (Spannable) textView.getText();
spannableText.setSpan(
     new ForegroundColorSpan(color),
     8, spannableText.getLength(),
     SPAN_INCLUSIVE_INCLUSIVE);

In questo esempio, BufferType.SPANNABLE fa sì che TextView crei un SpannableString, mentre L'oggetto CharSequence conservato da TextView ora ha un markup modificabile e e immutabile. Per aggiornare l'intervallo, recupera il testo come Spannable e poi aggiornare gli intervalli in base alle esigenze.

Quando alleghi, scolleghi o riposiziona gli intervalli, TextView automaticamente si aggiornano per riflettere la modifica al testo. Se modifichi un attributo interno di un intervallo esistente, chiama invalidate() per apportare modifiche relative all'aspetto oppure requestLayout() per apportare modifiche correlate alle metriche.

Imposta il testo in una TextView più volte

In alcuni casi, ad esempio quando si utilizza un RecyclerView.ViewHolder, puoi riutilizzare TextView e impostare il testo più volte. Di predefinito, a prescindere dalla configurazione di BufferType, TextView crea una copia dell'oggetto CharSequence e la contiene in memoria. In questo modo TextView aggiornamenti intenzionali: non puoi aggiornare l'originale CharSequence per aggiornare il testo. Questo significa che ogni volta che imposti di testo, TextView crea un nuovo oggetto.

Se vuoi avere un maggiore controllo su questo processo ed evitare l'oggetto aggiuntivo puoi implementare le soluzioni Spannable.Factory e override newSpannable(). Anziché creare un nuovo oggetto di testo, puoi trasmettere e restituire l'oggetto esistente CharSequence come Spannable, come mostrato nell'esempio seguente:

Kotlin

val spannableFactory = object : Spannable.Factory() {
    override fun newSpannable(source: CharSequence?): Spannable {
        return source as Spannable
    }
}

Java

Spannable.Factory spannableFactory = new Spannable.Factory(){
    @Override
    public Spannable newSpannable(CharSequence source) {
        return (Spannable) source;
    }
};

Devi usare textView.setText(spannableObject, BufferType.SPANNABLE) quando impostare il testo. In caso contrario, l'origine CharSequence viene creata come Spanned dell'istanza e non può essere trasmesso a Spannable, causando la generazione di un messaggio di errore da parte di newSpannable() ClassCastException.

Dopo aver eseguito l'override di newSpannable(), chiedi a TextView di utilizzare il nuovo Factory:

Kotlin

textView.setSpannableFactory(spannableFactory)

Java

textView.setSpannableFactory(spannableFactory);

Imposta l'oggetto Spannable.Factory una volta, subito dopo aver ottenuto un riferimento al tuo TextView. Se utilizzi un RecyclerView, imposta l'oggetto Factory quando innanzitutto ad aumentare le visualizzazioni. In questo modo si evita la creazione di oggetti aggiuntivi RecyclerView associa un nuovo elemento a ViewHolder.

Modifica attributi dell'intervallo interno

Se devi modificare solo un attributo interno di un intervallo modificabile, ad esempio in un intervallo di punti personalizzato, puoi evitare l'overhead di chiamare setText() più volte mantenendo un riferimento all'intervallo al momento della creazione. Quando devi modificare l'intervallo, puoi modificare il riferimento e richiamare invalidate() o requestLayout() su TextView, a seconda del tipo di che hai modificato.

Nel codice di esempio che segue, un'implementazione di un elenco puntato personalizzato ha un colore predefinito rosso che diventa grigio quando viene toccato un pulsante:

Kotlin

class MainActivity : AppCompatActivity() {

    // Keeping the span as a field.
    val bulletSpan = BulletPointSpan(color = Color.RED)

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val spannable = SpannableString("Text is spantastic")
        // Setting the span to the bulletSpan field.
        spannable.setSpan(
            bulletSpan,
            0, 4,
            Spanned.SPAN_INCLUSIVE_INCLUSIVE
        )
        styledText.setText(spannable)
        button.setOnClickListener {
            // Change the color of the mutable span.
            bulletSpan.color = Color.GRAY
            // Color doesn't change until invalidate is called.
            styledText.invalidate()
        }
    }
}

Java

public class MainActivity extends AppCompatActivity {

    private BulletPointSpan bulletSpan = new BulletPointSpan(Color.RED);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        SpannableString spannable = new SpannableString("Text is spantastic");
        // Setting the span to the bulletSpan field.
        spannable.setSpan(bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        styledText.setText(spannable);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Change the color of the mutable span.
                bulletSpan.setColor(Color.GRAY);
                // Color doesn't change until invalidate is called.
                styledText.invalidate();
            }
        });
    }
}

Utilizzare le funzioni dell'estensione Android KTX

Android KTX contiene anche funzioni di estensione che semplificano l'utilizzo degli intervalli è più facile. Per saperne di più, consulta la documentazione relativa a androidx.core.text pacchetto.