Строковые ресурсы (просмотры)

Основные понятия и реализация Jetpack Compose

Строковый ресурс предоставляет текстовые строки для вашего приложения с дополнительным стилем и форматированием текста. Существует три типа ресурсов, которые могут предоставлять вашему приложению строки:

String
XML-ресурс, который предоставляет одну строку.
Массив строк
Ресурс XML, который предоставляет массив строк.
Строки с количеством (множественное число)
XML-ресурс, содержащий разные строки для множественного числа.

Все строки могут содержать разметку стилей и аргументы форматирования. Подробнее о форматировании и стилизации строк…

Строка

Одна строка, на которую можно ссылаться из приложения или из других файлов ресурсов (например, макета XML).

местоположение файла;
res/values/filename.xml
Название файла может быть любым. В качестве идентификатора ресурса используется атрибут name элемента <string>.
скомпилированный тип данных ресурса:
Указатель ресурса на String.
ссылка на ресурс:
В Java: R.string.string_name
В XML: @string/string_name
Синтаксис:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string
        name="string_name"
        >text_string</string>
</resources>
elements:
<resources>
Обязательный атрибут. Это должен быть корневой узел.

Нет атрибутов.

<string>
Строка, которая может включать теги стилей. Обратите внимание, что апострофы и кавычки необходимо экранировать. Подробнее о форматировании и стилях строк…

атрибуты:

name
Строка. Название строки. Это название используется в качестве идентификатора ресурса.
пример:
XML-файл сохранен в res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello!</string>
</resources>

Этот XML-код макета применяет строку к представлению:

<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello" />

Этот код приложения извлекает строку:

Kotlin

val string: String = getString(R.string.hello)

Java

String string = getString(R.string.hello);

Для получения строки можно использовать getString(int) или getText(int). getText(int) сохраняет любое форматирование, примененное к строке.

Массив строк

Массив строк, на которые можно ссылаться из приложения.

местоположение файла;
res/values/filename.xml
Название файла может быть любым. В качестве идентификатора ресурса используется атрибут name элемента <string-array>.
скомпилированный тип данных ресурса:
Указатель ресурса на массив объектов String.
ссылка на ресурс:
В Java: R.array.string_array_name
В XML: @[package:]array/string_array_name
Синтаксис:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array
        name="string_array_name">
        <item
            >text_string</item>
    </string-array>
</resources>
elements:
<resources>
Обязательный атрибут. Это должен быть корневой узел.

Нет атрибутов.

<string-array>
Определяет массив строк. Содержит один или несколько элементов <item>.

атрибуты:

name
Строка. Название массива. Это название используется в качестве идентификатора ресурса для ссылки на массив.
<item>
Строка, которая может включать теги стилей. Значение может быть ссылкой на другой строковый ресурс. Должен быть дочерним элементом <string-array>. Обратите внимание, что апострофы и кавычки нужно экранировать. Информацию о том, как правильно оформлять и форматировать строки, вы найдете в разделе Форматирование и стили ниже.

Нет атрибутов.

пример:
XML-файл сохранен в res/values/strings.xml:
<?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);

Строки с количеством (множественное число)

В разных языках действуют разные правила грамматического согласования с количеством. Например, в английском языке количество 1 – это особый случай. Мы пишем "1 книга", но для любого другого количества мы пишем "n книг". Различие между единственным и множественным числом очень распространено, но в некоторых языках есть и другие формы. Полный набор поддерживаемых Android типов: zero, one, two, few, many и other.

Правила выбора формы для определенного языка и количества могут быть очень сложными, поэтому в Android предусмотрены методы, такие как getQuantityString(), которые позволяют выбрать подходящий ресурс.

В API 24 и более поздних версиях можно использовать более мощный класс ICU MessageFormat.

местоположение файла;
res/values/filename.xml
Название файла может быть любым. В качестве идентификатора ресурса используется атрибут name элемента <plurals>.
ссылка на ресурс:
В Java: R.plurals.plural_name
Синтаксис:
<?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:
<resources>
Обязательный атрибут. Это должен быть корневой узел.

Нет атрибутов.

<plurals>
Набор строк, одна из которых выбирается в зависимости от количества чего-либо. Содержит один или несколько элементов <item>.

атрибуты:

name
Строка. Название пары строк. Это название используется в качестве идентификатора ресурса.
</dd>

<item>
Строка во множественном или единственном числе. Значение может быть ссылкой на другой строковый ресурс. Должен быть дочерним элементом <plurals>. Обратите внимание, что апострофы и кавычки необходимо экранировать. Подробнее о том, как правильно оформлять и форматировать строки, рассказывается в разделе Форматирование и стили.

атрибуты:

quantity
Ключевое слово. Значение, указывающее, когда следует использовать эту строку. Допустимые значения (в скобках приведены неполные примеры):
ЗначениеОписание
zeroКогда в языке требуется особое обращение с числом 0 (как в арабском).
oneИспользуется, когда в языке требуется особое обращение с числами, похожими на единицу (например, с числом 1 в английском и большинстве других языков; в русском языке к этому классу относится любое число, оканчивающееся на 1, но не на 11).
twoКогда в языке требуется особое обращение с числами, такими как два (например, 2 в валлийском или 102 в словенском).
fewКогда в языке есть особые правила для "малых" чисел (например, для 2, 3 и 4 в чешском или для чисел, заканчивающихся на 2, 3 или 4, но не на 12, 13 или 14, в польском).
manyЕсли в языке есть особые правила для "больших" чисел (например, для чисел, заканчивающихся на 11–99, в мальтийском языке).
otherКогда язык не требует специальной обработки заданного количества (как со всеми числами в китайском языке или 42 в английском).

пример:
XML-файл сохранен в 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>

XML-файл сохранен в 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>

Использование:

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

При использовании метода getQuantityString() вам нужно передать count дважды, если строка содержит форматирование. Например, для строки %d songs found первый параметр count выбирает подходящую строку во множественном числе, а второй параметр count вставляется в плейсхолдер %d. Если строки во множественном числе не содержат форматирования, передавать третий параметр в getQuantityString не нужно.

Формат и стиль

Вот несколько важных вещей, которые вы должны знать о том, как правильно форматировать и стилизовать строковые ресурсы.

Строки форматирования

Если вам нужно отформатировать строки, вы можете сделать это, поместив аргументы формата в строковый ресурс, как показано в следующем примере.

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

В этом примере строка формата имеет два аргумента: %1$s – строка, а %2$d – десятичное число. Затем отформатируйте строку, вызвав getString(int, Object...). Пример:

Kotlin

var text = getString(R.string.welcome_messages, username, mailCount)

Java

String text = getString(R.string.welcome_messages, username, mailCount);

Оформление с помощью HTML-разметки

Вы можете добавить в строки разметку HTML. Пример:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="welcome">Welcome to <b>Android</b>!</string>
</resources>

Если форматирование не применяется, можно задать текст TextView напрямую, вызвав setText(java.lang.CharSequence). Однако в некоторых случаях может потребоваться создать ресурс форматированного текста, который также используется в качестве строки формата. Обычно это не работает, потому что методы format(String, Object...) и getString(int, Object...) удаляют из строки всю информацию о стиле. Чтобы обойти это ограничение, можно использовать HTML-теги с экранированными символами, которые затем восстанавливаются с помощью fromHtml(String) после форматирования. Пример:

  1. Сохраните ресурс форматированного текста в виде экранированной HTML-строки:
    <resources>
      <string name="welcome_messages">Hello, %1$s! You have &lt;b>%2$d new messages&lt;/b>.</string>
    </resources>

    В эту строку добавляется элемент <b>. Обратите внимание, что открывающая скобка экранирована с помощью обозначения &lt;.

  2. Затем отформатируйте строку как обычно, но также вызовите fromHtml(String), чтобы преобразовать текст HTML в стилизованный текст:

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

Поскольку метод fromHtml(String) форматирует все объекты HTML, обязательно экранируйте все возможные символы HTML в строках, которые вы используете с форматированным текстом, с помощью htmlEncode(String). Например, если вы форматируете строку, содержащую такие символы, как "<" или "&", их необходимо экранировать перед форматированием, чтобы при передаче отформатированной строки через fromHtml(String) символы выводились в том виде, в котором они были изначально написаны. Пример:

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

Оформление с помощью Spannable

Spannable – это текстовый объект, стиль которого можно настраивать с помощью свойств гарнитуры, таких как цвет и насыщенность. Вы используете SpannableStringBuilder для создания текста, а затем применяете к нему стили, определенные в пакете android.text.style.

Для создания текста с разметкой можно использовать следующие вспомогательные методы:

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

Методы bold, italic и color, приведенные ниже, включают вспомогательные методы, описанные выше, и демонстрируют конкретные примеры применения стилей, определенных в пакете android.text.style. Вы можете создать аналогичные методы для других типов стилизации текста.

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

Вот пример того, как объединить эти методы, чтобы применить различные стили к отдельным словам во фразе:

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

Модуль core-ktx Kotlin также содержит функции расширения, которые упрощают работу со span-объектами. Дополнительную информацию можно найти в документации по пакету android.text на GitHub.

Дополнительную информацию о работе с диапазонами можно найти по следующим ссылкам:

Стилизация с помощью аннотаций

Вы можете применять сложные или собственные стили, используя класс Annotation вместе с тегом <annotation> в файлах ресурсов strings.xml. Тег аннотации позволяет помечать части строки для применения собственного стиля. Для этого в XML-файле нужно определить пары "ключ-значение", которые фреймворк преобразует в диапазоны Annotation. Затем вы можете получить эти аннотации и использовать ключ и значение, чтобы применить стиль.

При создании аннотаций обязательно добавляйте тег <annotation> во все переводы строки в каждом файле strings.xml.


Применение пользовательской гарнитуры к слову "текст" на всех языках

Пример добавления собственной гарнитуры

  1. Добавьте тег <annotation> и определите пару "ключ-значение". В этом случае ключ – font, а значение – тип шрифта, который мы хотим использовать: 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>
  2. Загрузите строковый ресурс и найдите аннотации с ключом font. Затем создайте пользовательский диапазон и замените им существующий.

    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;

Если вы используете один и тот же текст несколько раз, вам следует создать объект SpannableString один раз и повторно использовать его по мере необходимости, чтобы избежать потенциальных проблем с производительностью и памятью.

Дополнительные примеры использования аннотаций можно найти в статье Стилизация интернационализированного текста в Android.

Диапазоны аннотаций и разбиение текста на фрагменты

Поскольку сегменты Annotation также являются сегментами ParcelableSpans, пары "ключ-значение" упаковываются и распаковываются. Если получатель посылки знает, как интерпретировать аннотации, вы можете использовать диапазоны Annotation, чтобы применить собственный стиль к тексту посылки.

Чтобы сохранить собственный стиль при передаче текста в Intent Bundle, сначала нужно добавить в текст диапазоны Annotation. Это можно сделать в ресурсах XML с помощью тега <annotation>, как показано в примере выше, или в коде, создав новый элемент Annotation и задав его в качестве диапазона, как показано ниже.

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

Получите текст из Bundle в виде SpannableString, а затем проанализируйте прикрепленные аннотации, как показано в примере выше.

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

Дополнительную информацию о стилях текста можно найти по следующим ссылкам: