Pojęcia i implementacja w Jetpack Compose
Zasób tekstowy zawiera ciągi tekstowe dla aplikacji z opcjonalnym stylem i formatowaniem tekstu. Istnieją 3 rodzaje zasobów, które mogą dostarczać aplikacji ciągi znaków:
- String
- Zasób XML, który zawiera pojedynczy ciąg znaków.
- Tablica ciągów znaków
- Zasób XML, który zawiera tablicę ciągów znaków.
- Ciągi znaków ilości (liczba mnoga)
- Plik zasobu XML zawierający różne ciągi znaków do tworzenia form liczby mnogiej.
Wszystkie ciągi tekstowe mogą stosować pewne znaczniki stylów i argumenty formatowania. Informacje o stylizowaniu i formatowaniu ciągów znaków znajdziesz w sekcji Formatowanie i stylizowanie.
Ciąg znaków
Pojedynczy ciąg znaków, do którego można się odwoływać z aplikacji lub z innych plików zasobów (np. układu XML).
- lokalizacja pliku:
res/values/filename.xml
Nazwa pliku jest dowolna. Element<string>z atrybutemnamejest używany jako identyfikator zasobu.- skompilowany typ danych zasobu:
- Wskaźnik zasobu do
String. - odwołanie do zasobu:
-
W języku Java:
R.string.string_name
W formacie XML:@string/string_name - składnia:
-
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="string_name" >text_string</string> </resources>
- elements:
- przykład:
- Plik XML zapisany w lokalizacji
res/values/strings.xml:<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello!</string> </resources>
Ten plik XML układu stosuje ciąg znaków do obiektu View:
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" />
Ten kod aplikacji pobiera ciąg znaków:
Aby pobrać ciąg znaków, możesz użyć
getString(int)lubgetText(int).getText(int)zachowuje formatowanie tekstu sformatowanego zastosowane do ciągu znaków.
Tablica tekstowa
Tablica ciągów tekstowych, do których można się odwoływać z aplikacji.
- lokalizacja pliku:
res/values/filename.xml
Nazwa pliku jest dowolna. Element<string-array>z atrybutemnamejest używany jako identyfikator zasobu.- skompilowany typ danych zasobu:
- Wskaźnik zasobu do tablicy elementów
String. - odwołanie do zasobu:
-
W języku Java:
R.array.string_array_name
W formacie XML:@[package:]array/string_array_name - składnia:
-
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="string_array_name"> <item >text_string</item> </string-array> </resources>
- elements:
- przykład:
- Plik XML zapisany w lokalizacji
res/values/strings.xml: Ten kod aplikacji pobiera tablicę tekstową:<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="planets_array"> <item>Mercury</item> <item>Venus</item> <item>Earth</item> <item>Mars</item> </string-array> </resources>
Kotlin
val array: Array<String> =
resources.getStringArray(R.array.planets_array)Java
Resources res =
getResources(); String[] planets = res.getStringArray(R.array.planets_array);
Ciągi ilościowe (liczba mnoga)
W różnych językach obowiązują różne zasady dotyczące zgodności gramatycznej z liczbą. Na przykład w języku angielskim liczba 1 jest przypadkiem szczególnym. Piszesz „1 książka”, ale w przypadku innych ilości piszesz „n książek”. To rozróżnienie między liczbą pojedynczą a mnogą jest bardzo powszechne, ale w innych językach występuje więcej rozróżnień. Pełny zestaw obsługiwany przez Androida to zero, one, two, few, many i other.
Reguły decydujące o tym, którego przypadku użyć w danym języku i dla danej ilości, mogą być bardzo złożone, dlatego Android udostępnia metody takie jak getQuantityString(), które pozwalają wybrać odpowiedni zasób.
W przypadku interfejsu API w wersji 24 lub nowszej możesz zamiast tego użyć znacznie bardziej zaawansowanej klasy ICU MessageFormat.
- lokalizacja pliku:
res/values/filename.xml
Nazwa pliku jest dowolna. Atrybutnameelementu<plurals>jest używany jako identyfikator zasobu.- odwołanie do zasobu:
-
W Javie:
R.plurals.plural_name - składnia:
-
<?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="plural_name"> <item quantity=["zero" | "one" | "two" | "few" | "many" | "other"] >text_string</item> </plurals> </resources>
- elements:
- przykład:
- Plik XML zapisany w lokalizacji
res/values/strings.xml:<?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="numberOfSongsAvailable"> <!-- As a developer, you should always supply "one" and "other" strings. Your translators will know which strings are actually needed for their language. Always include %d in "one" because translators will need to use %d for languages where "one" doesn't mean 1 (as explained above). --> <item quantity="one">%d song found.</item> <item quantity="other">%d songs found.</item> </plurals> </resources>
Plik XML zapisany w lokalizacji
res/values-pl/strings.xml:<?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="numberOfSongsAvailable"> <item quantity="one">Znaleziono %d piosenkę.</item> <item quantity="few">Znaleziono %d piosenki.</item> <item quantity="other">Znaleziono %d piosenek.</item> </plurals> </resources>
Wykorzystanie:
Kotlin
val count = getNumberOfSongsAvailable() val songsFound = resources.
getQuantityString(R.plurals.numberOfSongsAvailable, count, count)Java
int count = getNumberOfSongsAvailable(); Resources res =
getResources(); String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count);Jeśli używasz metody
getQuantityString(), musisz przekazać parametrcount2 razy, jeśli ciąg znaków zawiera formatowanie ciągu znaków. Na przykład w przypadku ciągu znaków%d songs foundpierwszy parametrcountwybiera odpowiedni ciąg znaków w liczbie mnogiej, a drugi parametrcountjest wstawiany w miejsce symbolu zastępczego%d. Jeśli ciągi w liczbie mnogiej nie zawierają formatowania ciągów, nie musisz przekazywać trzeciego parametru do funkcjigetQuantityString.
Format i styl
Oto kilka ważnych informacji o prawidłowym formatowaniu i stylizowaniu zasobów w postaci ciągów znaków.
Ciągi formatujące
Jeśli chcesz sformatować ciągi znaków, możesz to zrobić, umieszczając argumenty formatu w zasobie tekstowym, jak pokazano w tym przykładzie zasobu:
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
W tym przykładzie ciąg formatu ma 2 argumenty: %1$s to ciąg znaków, a %2$d to liczba dziesiętna. Następnie sformatuj ciąg znaków, wywołując funkcję
getString(int, Object...). Przykład:
Kotlin
var text = getString(R.string.welcome_messages, username, mailCount)
Java
String text = getString(R.string.welcome_messages, username, mailCount);
Stylizowanie za pomocą znaczników HTML
Do ciągów znaków możesz dodawać style za pomocą znaczników HTML. Przykład:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="welcome">Welcome to <b>Android</b>!</string> </resources>
Jeśli nie stosujesz formatowania, możesz ustawić tekst TextView bezpośrednio, wywołując setText(java.lang.CharSequence). W niektórych przypadkach możesz jednak chcieć utworzyć zasób tekstu ze stylem, który będzie też używany jako ciąg formatujący.
Zwykle to nie działa, ponieważ metody format(String, Object...) i getString(int, Object...) usuwają z ciągu tekstowego wszystkie informacje o stylu. Aby to obejść, możesz napisać tagi HTML z sekwencjami ucieczki, które są następnie odzyskiwane za pomocą funkcji fromHtml(String) po sformatowaniu. Przykład:
- Zapisz zasób tekstu ze stylami jako ciąg tekstowy z kodowaniem HTML:
<resources> <string name="welcome_messages">Hello, %1$s! You have <b>%2$d new messages</b>.</string> </resources>
Do tego sformatowanego ciągu dodawany jest element
<b>. Zwróć uwagę, że nawias otwierający jest zakodowany w HTML-u za pomocą notacji<. - Następnie sformatuj ciąg znaków w zwykły sposób, ale wywołaj też funkcję
fromHtml(String), aby przekonwertować tekst HTML na tekst ze stylami:Kotlin
val text: String = getString(R.string.welcome_messages, username, mailCount) val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
Java
String text = getString(R.string.welcome_messages, username, mailCount); Spanned styledText = Html.fromHtml(text, FROM_HTML_MODE_LEGACY);
Metoda fromHtml(String) formatuje wszystkie encje HTML, więc pamiętaj, aby w ciągach używanych z sformatowanym tekstem zmienić znaczenie wszystkich możliwych znaków HTML za pomocą htmlEncode(String). Jeśli na przykład formatujesz ciąg znaków zawierający znaki takie jak „<” lub „&”, przed formatowaniem musisz je zamienić na sekwencje ucieczki, aby po przekazaniu sformatowanego ciągu znaków przez fromHtml(String) znaki były wyświetlane w sposób, w jaki zostały pierwotnie zapisane. Przykład:
Kotlin
val escapedUsername: String = TextUtils.htmlEncode(username)
val text: String = getString(R.string.welcome_messages, escapedUsername, mailCount)
val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)Java
String escapedUsername = TextUtils.htmlEncode(username);
String text = getString(R.string.welcome_messages, escapedUsername, mailCount);
Spanned styledText = Html.fromHtml(text);Stylizowanie za pomocą obiektów Spannable
Spannable to obiekt tekstowy, który możesz stylizować za pomocą właściwości czcionki, takich jak kolor i grubość. Za pomocą SpannableStringBuilder możesz tworzyć tekst, a następnie stosować do niego style zdefiniowane w pakiecie android.text.style.
Do skonfigurowania większości czynności związanych z tworzeniem tekstu z możliwością zmiany stylu możesz użyć tych metod pomocniczych:
Kotlin
/** * Returns a CharSequence that concatenates the specified array of CharSequence * objects and then applies a list of zero or more tags to the entire range. * * @param content an array of character sequences to apply a style to * @param tags the styled span objects to apply to the content * such as android.text.style.StyleSpan */ private fun apply(content: Array<out CharSequence>, vararg tags: Any): CharSequence { return SpannableStringBuilder().apply { openTags(tags) content.forEach { charSequence -> append(charSequence) } closeTags(tags) } } /** * Iterates over an array of tags and applies them to the beginning of the specified * Spannable object so that future text appended to the text will have the styling * applied to it. Do not call this method directly. */ private fun Spannable.openTags(tags: Array<out Any>) { tags.forEach { tag -> setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK) } } /** * "Closes" the specified tags on a Spannable by updating the spans to be * endpoint-exclusive so that future text appended to the end will not take * on the same styling. Do not call this method directly. */ private fun Spannable.closeTags(tags: Array<out Any>) { tags.forEach { tag -> if (length > 0) { setSpan(tag, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } else { removeSpan(tag) } } }
Java
/** * Returns a CharSequence that concatenates the specified array of CharSequence * objects and then applies a list of zero or more tags to the entire range. * * @param content an array of character sequences to apply a style to * @param tags the styled span objects to apply to the content * such as android.text.style.StyleSpan * */ private static CharSequence applyStyles(CharSequence[] content, Object[] tags) { SpannableStringBuilder text = new SpannableStringBuilder(); openTags(text, tags); for (CharSequence item : content) { text.append(item); } closeTags(text, tags); return text; } /** * Iterates over an array of tags and applies them to the beginning of the specified * Spannable object so that future text appended to the text will have the styling * applied to it. Do not call this method directly. */ private static void openTags(Spannable text, Object[] tags) { for (Object tag : tags) { text.setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK); } } /** * "Closes" the specified tags on a Spannable by updating the spans to be * endpoint-exclusive so that future text appended to the end will not take * on the same styling. Do not call this method directly. */ private static void closeTags(Spannable text, Object[] tags) { int len = text.length(); for (Object tag : tags) { if (len > 0) { text.setSpan(tag, 0, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { text.removeSpan(tag); } } }
Poniższe metody bold, italic i color opakowują metody pomocnicze
opisane powyżej i pokazują konkretne przykłady stosowania stylów zdefiniowanych w pakiecie android.text.style. Możesz utworzyć podobne metody, aby zastosować inne rodzaje stylów tekstu.
Kotlin
/** * Returns a CharSequence that applies boldface to the concatenation * of the specified CharSequence objects. */ fun bold(vararg content: CharSequence): CharSequence = apply(content, StyleSpan(Typeface.BOLD)) /** * Returns a CharSequence that applies italics to the concatenation * of the specified CharSequence objects. */ fun italic(vararg content: CharSequence): CharSequence = apply(content, StyleSpan(Typeface.ITALIC)) /** * Returns a CharSequence that applies a foreground color to the * concatenation of the specified CharSequence objects. */ fun color(color: Int, vararg content: CharSequence): CharSequence = apply(content, ForegroundColorSpan(color))
Java
/** * Returns a CharSequence that applies boldface to the concatenation * of the specified CharSequence objects. */ public static CharSequence bold(CharSequence... content) { return apply(content, new StyleSpan(Typeface.BOLD)); } /** * Returns a CharSequence that applies italics to the concatenation * of the specified CharSequence objects. */ public static CharSequence italic(CharSequence... content) { return apply(content, new StyleSpan(Typeface.ITALIC)); } /** * Returns a CharSequence that applies a foreground color to the * concatenation of the specified CharSequence objects. */ public static CharSequence color(int color, CharSequence... content) { return apply(content, new ForegroundColorSpan(color)); }
Ten przykład pokazuje, jak połączyć te metody, aby zastosować różne style do poszczególnych słów w zdaniu:
Kotlin
// Create an italic "hello, " a red "world", // and bold the entire sequence. val text: CharSequence = bold(italic(getString(R.string.hello)), color(Color.RED, getString(R.string.world)))
Java
// Create an italic "hello, " a red "world", // and bold the entire sequence. CharSequence text = bold(italic(getString(R.string.hello)), color(Color.RED, getString(R.string.world)));
Moduł Kotlin core-ktx zawiera też funkcje rozszerzające, które jeszcze bardziej ułatwiają pracę z zakresami. Więcej informacji znajdziesz w android.textdokumentacji pakietu na GitHubie.
Więcej informacji o pracy z zakresami znajdziesz w tych artykułach:
Stylizowanie za pomocą adnotacji
Możesz zastosować złożone lub niestandardowe style, używając klasy Annotation wraz z tagiem <annotation> w plikach zasobów strings.xml. Tag adnotacji umożliwia oznaczanie części ciągu znaków w celu zastosowania do nich niestandardowych stylów przez zdefiniowanie w pliku XML niestandardowych par klucz-wartość, które framework przekształca następnie w Annotation zakresy. Możesz potem pobrać te adnotacje i użyć klucza oraz wartości, aby zastosować styl.
Podczas tworzenia adnotacji dodaj tag <annotation>
do wszystkich tłumaczeń ciągu znaków w każdym pliku strings.xml.

Stosowanie niestandardowej czcionki do słowa „text” we wszystkich językach
Przykład – dodawanie niestandardowego kroju pisma
-
Dodaj tag
<annotation>i zdefiniuj parę klucz-wartość. W tym przypadku kluczem jest font, a wartością typ czcionki, której chcemy użyć: title_emphasis.// values/strings.xml <string name="title">Best practices for <annotation font="title_emphasis">text</annotation> on Android</string> // values-es/strings.xml <string name="title"><annotation font="title_emphasis">Texto</annotation> en Android: mejores prácticas</string>
-
Wczytaj zasób tekstowy i znajdź adnotacje za pomocą klucza font. Następnie utwórz niestandardowy zakres i zastąp nim dotychczasowy zakres.
Kotlin
// get the text as SpannedString so we can get the spans attached to the text val titleText = getText(R.string.title) as SpannedString // get all the annotation spans from the text val annotations = titleText.getSpans(0, titleText.length, Annotation::class.java) // create a copy of the title text as a SpannableString. // the constructor copies both the text and the spans. so we can add and remove spans val spannableString = SpannableString(titleText) // iterate through all the annotation spans for (annotation in annotations) { // look for the span with the key font if (annotation.key == "font") { val fontName = annotation.value // check the value associated to the annotation key if (fontName == "title_emphasis") { // create the typeface val typeface = getFontCompat(R.font.permanent_marker) // set the span at the same indices as the annotation spannableString.setSpan(CustomTypefaceSpan(typeface), titleText.getSpanStart(annotation), titleText.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } } // now, the spannableString contains both the annotation spans and the CustomTypefaceSpan styledText.text = spannableString
Java
// get the text as SpannedString so we can get the spans attached to the text SpannedString titleText = (SpannedString) getText(R.string.title); // get all the annotation spans from the text Annotation[] annotations = titleText.getSpans(0, titleText.length(), Annotation.class); // create a copy of the title text as a SpannableString. // the constructor copies both the text and the spans. so we can add and remove spans SpannableString spannableString = new SpannableString(titleText); // iterate through all the annotation spans for (Annotation annotation: annotations) { // look for the span with the key font if (annotation.getKey().equals("font")) { String fontName = annotation.getValue(); // check the value associated to the annotation key if (fontName.equals("title_emphasis")) { // create the typeface Typeface typeface = ResourcesCompat.getFont(this, R.font.roboto_mono); // set the span at the same indices as the annotation spannableString.setSpan(new CustomTypefaceSpan(typeface), titleText.getSpanStart(annotation), titleText.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } } // now, the spannableString contains both the annotation spans and the CustomTypefaceSpan styledText.text = spannableString;
Jeśli używasz tego samego tekstu wiele razy, utwórz obiekt SpannableString tylko raz i używaj go w razie potrzeby, aby uniknąć potencjalnych problemów z wydajnością i pamięcią.
Więcej przykładów użycia adnotacji znajdziesz w artykule Stylizacja tekstu w różnych językach w Androidzie.
Zakresy adnotacji i podział tekstu
Ponieważ zakresy Annotation są również zakresami ParcelableSpans, pary klucz-wartość są dzielone i łączone. Jeśli odbiorca przesyłki wie, jak interpretować adnotacje, możesz użyć zakresów Annotation, aby zastosować niestandardowe style do tekstu przesyłki.
Aby zachować niestandardowy styl podczas przekazywania tekstu do pakietu intencji, musisz najpierw dodać do tekstu Annotation zakresy. Możesz to zrobić w zasobach XML za pomocą tagu <annotation>, jak pokazano w przykładzie powyżej, lub w kodzie, tworząc nowy Annotation i ustawiając go jako zakres, jak pokazano poniżej:
Kotlin
val spannableString = SpannableString("My spantastic text") val annotation = Annotation("font", "title_emphasis") spannableString.setSpan(annotation, 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) // start Activity with text with spans val intent = Intent(this, MainActivity::class.java) intent.putExtra(TEXT_EXTRA, spannableString) startActivity(intent)
Java
SpannableString spannableString = new SpannableString("My spantastic text"); Annotation annotation = new Annotation("font", "title_emphasis"); spannableString.setSpan(annotation, 3, 7, 33); // start Activity with text with spans Intent intent = new Intent(this, MainActivity.class); intent.putExtra(TEXT_EXTRA, spannableString); this.startActivity(intent);
Pobierz tekst z Bundle jako SpannableString, a następnie przeanalizuj dołączone adnotacje, jak pokazano w przykładzie powyżej.
Kotlin
// read text with Spans val intentCharSequence = intent.getCharSequenceExtra(TEXT_EXTRA) as SpannableString
Java
// read text with Spans SpannableString intentCharSequence = (SpannableString)intent.getCharSequenceExtra(TEXT_EXTRA);
Więcej informacji o stylizacji tekstu znajdziesz w tych artykułach:
- Prezentacja na Google I/O 2018 – sprawdzone metody dotyczące tekstu na Androidzie
- Omówienie zakresów