Spany to zaawansowane obiekty znaczników, których możesz używać do określania stylu tekstu
na poziomie znaku lub akapitu. Dołączając spany do obiektów tekstowych, możesz zmienić
w ten sposób można zmieniać kolor tekstu,
dodawać do niego kolory,
przez skalowanie rozmiaru tekstu
oraz jego dostosowywanie. Spany można też
zmień właściwości TextPaint
, rysuj na
Canvas
i zmień układ tekstu.
Android udostępnia kilka rodzajów spanów, które obejmują zróżnicowany tekst wzorów stylizacji. Możesz też utworzyć własne rozpiętości, aby zastosować styl niestandardowy.
Tworzenie i stosowanie spanu
Aby utworzyć span, możesz użyć jednej z klas wymienionych w poniższej tabeli. Klasy różnią się w zależności od tego, czy sam tekst jest zmienny, od tego, czy tekst znaczniki można zmieniać oraz która struktura danych zawiera dane spanów.
Kategoria | Tekst zmienny | Zmienne znaczniki | Struktura danych |
---|---|---|---|
SpannedString |
Nie | Nie | Tablica liniowa |
SpannableString |
Nie | Tak | Tablica liniowa |
SpannableStringBuilder |
Tak | Tak | Drzewo interwału |
Wszystkie 3 zajęcia wykraczają poza Spanned
za pomocą prostego interfejsu online. SpannableString
i SpannableStringBuilder
rozszerzają też
Spannable
.
Aby zdecydować, którego z nich użyć:
- Jeśli po utworzeniu nie modyfikujesz tekstu ani znaczników, użyj
SpannedString
- Jeśli chcesz dołączyć niewielką liczbę spanów do pojedynczego obiektu tekstowego i
Sam tekst jest tylko do odczytu, użyj
SpannableString
. - Jeśli po utworzeniu musisz zmodyfikować tekst i musisz dołączyć do niego spany
tekst, użyj funkcji
SpannableStringBuilder
. - Jeśli musisz dołączyć do obiektu tekstowego dużą liczbę spanów,
aby określić, czy sam tekst jest tylko do odczytu, użyj
SpannableStringBuilder
.
Aby zastosować span, wywołaj setSpan(Object _what_, int _start_, int _end_, int
_flags_)
.
na obiekcie Spannable
. Parametr what odnosi się do zakresu, którym jesteś
a parametry start i end wskazują część
tekstu, do którego stosujesz rozpiętość.
Jeśli wstawisz tekst w granicach zakresu, rozpiętość zostanie automatycznie rozwinięta do wartości
będą zawierać wstawiony tekst. Podczas wstawiania tekstu na spanu
Granice – czyli na indeksach początku lub końcowego – flagi
określa, czy rozpiętość rozwija się, aby uwzględnić wstawiony tekst. Używaj
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, aby uwzględnić wstawiony tekst, oraz użyć
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
aby wykluczyć wstawiony tekst.
Poniższy przykład pokazuje, jak dołączyć plik
ForegroundColorSpan
na
ciąg znaków:
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 );
Ustawiony jest za pomocą Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, więc
rozwija się, aby uwzględnić wstawiony tekst na granicach spanów, tak jak widać to w interfejsie
następujący przykład:
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)");
Do tego samego tekstu możesz dołączyć wiele spanów. Ten przykład pokazuje, aby utworzyć pogrubiony i czerwony tekst:
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 );
Typy spanów Androida
Android udostępnia ponad 20 typów spanów android.text.style. Android dzieli spany na 2 główne sposoby:
- Wpływ rozpiętości na tekst: może on wpływać na wygląd tekstu lub tekst danych.
- Zakres spanu: niektóre spany można stosować do pojedynczych znaków, a inne do pojedynczych znaków musi obejmować cały akapit.
W kolejnych sekcjach znajdziesz bardziej szczegółowe informacje o tych kategoriach.
Rozpiętości wpływające na wygląd tekstu
Niektóre rozpiętości stosowane na poziomie znaku wpływają na wygląd tekstu, na przykład
zmianę koloru tekstu lub tła oraz dodanie podkreśleń lub przekreśleń. Te
rozpiętości przedłużają
CharacterStyle
.
Ten przykładowy kod pokazuje, jak zastosować UnderlineSpan
do podkreślenia
tekst:
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);
Rozpiętości, które wpływają tylko na wygląd tekstu, powodują ponowne wyświetlenie tekstu bez
co aktywuje ponowne obliczanie układu. Te spany implementują
UpdateAppearance
i przedłuż
CharacterStyle
Podklasy CharacterStyle
określają sposób rysowania tekstu przez przyznanie dostępu do
Zaktualizuj TextPaint
.
Rozpiętości wpływające na dane tekstowe
Inne rozpiętości stosowane na poziomie znaku wpływają na dane tekstowe, np. linię
wysokości i rozmiaru tekstu. Te rozpiętości rozciągają
MetricAffectingSpan
zajęcia.
Ten przykładowy kod tworzy
RelativeSizeSpan
zwiększa rozmiar tekstu o 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);
Zastosowanie spanu, które wpływa na dane tekstowe, powoduje, że obserwowany obiekt ponownie zmierzyć tekst pod kątem prawidłowego układu i renderowania – np. zmienić może powodować, że słowa pojawią się w różnych wierszach. Stosuję poprzednie powoduje ponowne zmierzenie, ponowne obliczenie układu tekstu i ponowne tekst.
Rozpiętości, które mają wpływ na dane tekstowe, obejmują klasę MetricAffectingSpan
,
klasa abstrakcyjna, która pozwala podklasom definiować, jak rozpiętość wpływa na pomiar tekstu
przyznając dostęp do: TextPaint
. Od MetricAffectingSpan
CharacterSpan
, podklasy wpływają na wygląd tekstu przy znaku
na poziomie 300%.
Rozpiętości, które mają wpływ na akapity
Rozpiętość może też wpływać na tekst na poziomie akapitu, na przykład zmieniając
wyrównanie lub margines bloku tekstu. Rozpiętości mające wpływ na całe akapity
zaimplementować ParagraphStyle
. Do
tych zakresów, dołączasz do całego akapitu, z wyłączeniem jego zakończenia
znaku nowego wiersza. Jeśli próbujesz zastosować rozpiętość akapitu do tekstu innego niż
cały akapit, Android w ogóle nie stosuje zakresu.
Rysunek 8 pokazuje, jak Android rozdziela akapity w tekście.
Poniższy przykładowy kod stosuje
QuoteSpan
do akapitu. Pamiętaj, że
jeśli dołączysz span w miejscu innym niż początek lub koniec
akapit, Android w ogóle nie stosuje tego stylu.
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);
Utwórz niestandardowe spany
Jeśli potrzebujesz więcej funkcji niż oferuje dotychczasowa wersja Androida możesz zastosować niestandardowy zakres. Podczas wdrażania własnego spanu zdecyduj czy rozpiętość ma wpływ na tekst na poziomie znaków czy akapitu, a także na układ lub wygląd tekstu. Dzięki temu określanie, które klasy bazowe można rozszerzyć i które interfejsy mogą być potrzebne do wdrożenia. Skorzystaj z tej tabeli:
Scenariusz | Klasa lub interfejs |
---|---|
Rozpiętość ma wpływ na tekst na poziomie znaku. | CharacterStyle |
Rozpiętość wpływa na wygląd tekstu. | UpdateAppearance |
Rozpiętość wpływa na dane tekstowe. | UpdateLayout |
Rozpiętość ma wpływ na tekst na poziomie akapitu. | ParagraphStyle |
Jeśli na przykład chcesz wdrożyć niestandardowy zakres, który zmienia rozmiar tekstu i
kolor, rozszerzenie RelativeSizeSpan
. Przez dziedziczenie, RelativeSizeSpan
Rozszerza zakres CharacterStyle
i implementuje 2 interfejsy Update
. Ponieważ
klasa zawiera już wywołania zwrotne dla updateDrawState
i updateMeasureState
,
możesz zastąpić te wywołania,
aby zastosować zachowanie niestandardowe.
ten kod tworzy niestandardowy span, który rozciąga się na RelativeSizeSpan
i
zastępuje wywołanie zwrotne updateDrawState
, aby ustawić kolor 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); } }
Ten przykład pokazuje, jak utworzyć niestandardowy span. Aby osiągnąć taki sam efekt,
przez zastosowanie do tekstu elementów RelativeSizeSpan
i ForegroundColorSpan
.
Przetestuj użycie spanu
Interfejs Spanned
umożliwia ustawianie spanów oraz pobieranie spanów z
tekstu. Podczas testów wdróż Android JUnit
test, aby sprawdzić, czy zostały dodane prawidłowe spany.
w odpowiednich lokalizacjach. Przykład Styl tekstu
zawiera rozpiętość, w ramach której do list punktowanych są stosowane znaczniki przez dołączenie
BulletPointSpan
do tekstu. Poniższy przykładowy kod pokazuje, jak przetestować
czy punkty są wyświetlane zgodnie z oczekiwaniami:
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)); }
Więcej przykładów testów znajdziesz tutaj MarkdownBuilderTest w GitHubie.
Przetestuj spany niestandardowe
Podczas testowania spanów sprawdź, czy TextPaint
zawiera oczekiwaną wartość
i żeby w Canvas
pojawiły się prawidłowe elementy. Dla:
możesz zastosować niestandardową implementację spanów, która dodaje punktor na początku
jakiś tekst. Punktor ma określony rozmiar i kolor oraz występuje luka
między lewym marginesem obszaru rysowanego a punktem.
Możesz przetestować działanie tej klasy, implementując test AndroidJUnit. sprawdzając, czy:
- Jeśli prawidłowo zastosujesz rozpiętość, zostanie dodany punktor o określonym rozmiarze jest widoczny na obszarze roboczym, a między lewą krawędzią jest odpowiednia przestrzeń na marginesie i w punkcie.
- Jeśli nie zastosujesz zakresu, nie pojawi się żadne zachowanie niestandardowe.
Wdrożenie tych testów możesz zobaczyć w sekcji Styl tekstu przykład w GitHubie.
Interakcje z Canvas możesz przetestować, śmiesznie się z kanwy, przekazując symulowane
do żądania
drawLeadingMargin()
i sprawdzenie, czy właściwe metody są wywoływane z poprawnymi
.
Więcej próbek testów spanów znajdziesz w BulletPointSpanTest.
Sprawdzone metody korzystania ze spanów
Jest kilka sposobów na ustawienie tekstu w polu TextView
, które oszczędzają pamięć
dostosowane do Twoich potrzeb.
Dołączanie lub odłączanie spanu bez zmiany tekstu bazowego
TextView.setText()
zawiera wiele przeciążeń, które obsługują spany w różny sposób. Możesz na przykład:
ustaw obiekt tekstowy Spannable
za pomocą tego kodu:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
Gdy wywołujesz to przeciążenie (setText()
), TextView
tworzy kopię pliku danych
Spannable
jako SpannedString
i zapisuje go w pamięci jako CharSequence
.
Oznacza to, że tekst i spany są stałe, a więc gdy trzeba
zaktualizuj tekst lub spany, utwórz nowy obiekt Spannable
i wywołaj
setText()
, co powoduje również ponowne pomiary i ponowne narysowanie
układ.
Aby wskazać, że spany muszą być zmienne, możesz zamiast tego użyć
setText(CharSequence text, TextView.BufferType
type)
jak w tym przykładzie:
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);
W tym przykładzie para klucz-wartość
BufferType.SPANNABLE
sprawia, że TextView
tworzy SpannableString
, a
Obiekt CharSequence
przechowywany przez TextView
ma teraz zmienne znaczniki i
tekstu stałego. Aby zaktualizować span, pobierz tekst jako Spannable
, a następnie
w razie potrzeby zaktualizuj spany.
Przy dołączaniu, odłączaniu lub zmianie położenia rozpiętości element TextView
aktualizuje się, aby odzwierciedlić zmianę w tekście. Jeśli zmienisz atrybut wewnętrzny
istniejącego rozpiętości, wywołaj invalidate()
, aby wprowadzić zmiany dotyczące wyglądu lub
requestLayout()
, aby wprowadzić zmiany związane z danymi.
Ustawianie tekstu w obiekcie TextView wiele razy
W niektórych przypadkach, na przykład w przypadku użycia
RecyclerView.ViewHolder
możesz ponownie użyć elementu TextView
i ustawić tekst wielokrotnie. Według
jest domyślne. Niezależnie od tego, czy ustawisz BufferType
, TextView
tworzy
kopii obiektu CharSequence
i przechowywania go w pamięci. Dzięki temu wszystkie
Aplikacja TextView
jest zamierzona – nie można zaktualizować pierwotnej wersji
CharSequence
obiekt, aby zaktualizować tekst. Oznacza to, że za każdym razem, gdy ustawisz nowy
tekstu, TextView
tworzy nowy obiekt.
Jeśli chcesz mieć większą kontrolę nad tym procesem i uniknąć dodatkowych obiektów
możesz wdrożyć własne
Spannable.Factory
i zastąp
newSpannable()
Zamiast tworzyć nowy obiekt tekstowy, możesz rzutować i zwracać istniejący
CharSequence
jako Spannable
, jak pokazano w tym przykładzie:
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; } };
Musisz użyć formy płatności textView.setText(spannableObject, BufferType.SPANNABLE)
, gdy
ustawienie tekstu. W przeciwnym razie źródło CharSequence
jest tworzone jako Spanned
.
i nie można rzutować na Spannable
, przez co newSpannable()
zgłasza
ClassCastException
.
Po zastąpieniu wartości newSpannable()
poproś TextView
o użycie nowego elementu Factory
:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Ustaw obiekt Spannable.Factory
raz, zaraz po uzyskaniu odniesienia do swojego
TextView
Jeśli używasz obiektu RecyclerView
, ustaw obiekt Factory
podczas
by zwiększyć liczbę wyświetleń. Zapobiega to tworzeniu dodatkowych obiektów, gdy
RecyclerView
tworzy nowy element w dokumencie ViewHolder
.
Zmień atrybuty spanów wewnętrznych
Jeśli chcesz zmienić tylko atrybut wewnętrzny o zmiennym zakresie, taki jak
w niestandardowym rozpiętości punktora, można uniknąć
narzutu na wywoływanie
setText()
wiele razy, zapisując odwołanie do spanu w trakcie jego tworzenia.
Aby zmodyfikować span, możesz zmodyfikować odwołanie, a następnie wywołać
invalidate()
lub requestLayout()
na: TextView
, w zależności od typu
.
W poniższym przykładzie kodu niestandardowa implementacja punktorów ma domyślny kolor czerwonego, który zmienia kolor na szary po dotknięciu przycisku:
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(); } }); } }
Korzystanie z funkcji rozszerzenia KTX na Androidzie
Android KTX zawiera również funkcje rozszerzeń, które pozwalają na pracę ze spanami. . Aby dowiedzieć się więcej, zapoznaj się z dokumentacją androidx.core.text pakietu SDK.