Хабрахабр

Отображение текста в Android

В данной статье пойдет речь о TextView. Отображение текстовой информации — наверное, самая базовая и важная часть многих Android-приложений. Периодически в работе с текстом приходится задумываться о реализации различных дизайнерских решений или улучшении производительности при отрисовке экрана. Каждый разработчик, начиная с «Hello World», постоянно сталкивается с этим элементом пользовательского интерфейса.

Основные советы были взяты из докладов прошедших Google I/O. Я расскажу об устройстве TextView и некоторых тонкостях работы с ним.

Их можно разделить на две основные части — java-код и нативный код: Для отрисовки текста в Android под капотом используется целый стек из различных библиотек.

Java-код по сути является частью Android SDK, доступной разработчикам приложений, и новые возможности из него могут быть перенесены в support library.

Ядро представляет из себя следующие библиотеки: Само ядро TextView написано на C++, что ограничивает портирование в support library реализованных там новых возможностей из новых версий операционной системы.

  • Minikin используется для измерения длины текста, переноса строк и слов по слогам.
  • ICU обеспечивает поддержку Unicode.
  • HarfBuzz находит для символов юникода соответствующие графические элементы (глифы) в ширифтах.
  • FreeType делает растровые изображения глифов.
  • Skia – движок для рисования 2D графики.

Измерение длины текста и перенос строк

Если передать строку библиотеке Minikin, которая используется внутри TextView, то первым делом она определяет, из каких глифов строка состоит:

Кроме того, стоит обратить внимание, что нужные глифы могут быть найдены в различных системных шрифтах. Как можно заметить из данного примера, сопоставление символов юникода с глифами не всегда будет один к одному: здесь сразу 3 символа будут соответствовать одному глифу ffi.

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

textView.typeface = TypeFace.CustomFallbackBuilder( FontFamily.Builder( Font.Builder(assets, “lato.ttf”).build() ).build()
).addCustomFallback( FontFamily.Builder( Font.Builder(assets, “kosugi.ttf”).build() ).build()
).build()

CustomFallbackBuilder имеет ограничение на количество font family – можно добавить не более 64 шрифтов. Теперь с использованием CustomFallbackBuilder при сопоставлении символов с глифами SDK будет перебирать указанные font family по порядку, и если не удастся найти соответствие, поиск продолжится в системных шрифтах (а через метод setSystemFallback() можно указать предпочитаемый системный font family).

Для ускорения работы, начиная с Lollipop (21), используется системный LRU кэш из слов. Библиотека Minikin разделяет строки на слова и делает измерение отдельных слов. Такой кэш дает огромный выигрыш в производительности: вызов Paint.measureText() для закешированного слова займет в среднем 3% от времени первого расчета его размеров.

Начиная с Marshmallow (23) можно управлять ее поведением, указав у TextView специальные атрибуты breakStrategy и hyphenationFrequency. Если текст не помещается в заданную ширину, Minikin расставляет переносы строк и слов в тексте.

При значении breakStrategy=simple библиотека просто будет расставлять переносы последовательно, проходя по тексту: как только строка перестает помещаться, ставится перенос перед последним словом.

В значении balanced библиотека постарается сделать переносы строк так, чтобы строки оказались выровнены по ширине.

high_quality имеет почти такое же поведение, что и balanced, за исключением некоторых отличий (одно из них: на предпоследней строке перенос может быть не только отдельных слов, но и слова по слогам).

Значение none не будет делать автоматический перенос слов, normal сделает небольшую частоту переносов, а full, соответственно, задействует максимальное количество слов. Атрибут hyphenationFrequency позволяет управлять стратегией переноса слов по слогам.

Производительность отрисовки текста в зависимости от выбранных флагов (измерялась на Android P (28)):

1. Учитывая достаточно сильный удар по производительности, разработчики Google, начиная с версии Q (29) и AppCompat 1. Если перенос слов важен в приложении, то теперь его надо включать явно. 0, решили по умолчанию выключить перенос слов (hyphenation).

В зависимости от языка система будет выбирать специальные словари с правилами переноса. При использовании переноса слов надо учитывать, что на работу библиотеки будет влиять текущий выбранный язык в операционной системе.

В Android есть несколько способов стилизации текста:

  • Единый стиль (single style), который применяется для всего элемента TextView.
  • Мультистиль (multi style) — сразу несколько стилей, которые могут быть применены к тексту, на уровне параграфа или отдельных символов. Для этого есть несколько способов:
    • рисование текста на канве
    • html-теги
    • специальные элементы разметки – span’ы

При этом система будет применять значения из ресурсов в следующем порядке: TextAppearance, тема (Theme), стиль по умолчанию (Default style), стиль из приложения, и наибольший приоритет — значения атрибутов View. Единый стиль подразумевает под собой использование XML-стилей или XML-атрибутов в разметке TextView.

Использование ресурсов — это достаточно простое решение, но, к сожалению, оно не позволяет применить стиль к части текста.

Все что нужно разработчику — сделать вызов метода Html.fromHtml(), который превратит текст с тегами в текст, размеченный span’ами. Html-теги – еще одно простое решение, которое дает такие возможности, как сделать стиль отдельных слов жирным, курсивным, или даже выделить в тексте списки при помощи точек. Но такое решение имеет ограниченные возможности, так как распознает только часть html-тегов и не поддерживает CSS стили.

val text = "My text <ul><li>bullet one</li><li>bullet two</li></ul>" myTextView.text = Html.fromHtml(text)

Различные способы стилизации TextView можно комбинировать, но стоит помнить о приоритете того или иного метода, что будет влиять на конечный результат:

Но такое решение в зависимости от требований может быть достаточно сложным в реализации и выходит за рамки этой статьи. Еще один способ — рисование текста на канве — дает разработчику полный контроль над выводом текста: например, можно нарисовать текст вдоль кривой линии.

С помощью span’ов можно изменить цвет диапазона символов, сделать часть текста в виде ссылок, изменить размер текста, нарисовать точку перед параграфом и т.д. Для тонкой настройки стилей в TextView используются span’ы.

Можно выделить следующие категории span’ов:

  • Character spans – применяются на уровне символов строки.
    • Appearance affecting – не меняют размер текста.
    • Metric affecting – изменяют размер текста.
  • Paragraph spans – применяются на уровне параграфа.

Android фреймворк, применяя span, проверяет, какие интерфейсы этот объект реализует, чтобы вызвать нужные методы. В Android фреймворке есть интерфейсы и абстрактные классы с методами, которые вызываются во время onMeasure() и отрисовки TextView, эти методы дают доступ span’ам к более низкоуровневым объектам вроде TextPaint и Canvas.

В android фреймворке определено порядка 20+ span’ов, так что прежде чем делать свой собственный, лучше проверить, нет ли в SDK подходящего.

Appearance vs metric affecting spans

Эти span’ы имплементируют интерфейс UpdateAppearance и наследуются от класса CharacterStyle, который предоставляет доступ к объекту TextPaint. Первая категория span’ов влияет на то, как будут выглядеть символы в строке: цвет символов, цвет фона, подчеркнутые или зачеркнутые символы и т.д.

Эти span’ы обычно наследуются от класса MetricAffectingSpan, который наследуется от упомянутого выше CharacterStyle. Metric affecting span влияет на размер текста и layout’а, следовательно применение такого span’а потребует не только перерисовку TextView, но и вызов onMeasure()/onLayout().

Character vs paragraph affecting spans

Такие span’ы должны наследоваться от класса ParagraphStyle и вставляться в текст ровно с начала параграфа до его конца. Paragraph span влияет на целый блок текста: может изменить выравнивание, отступ или даже вставить точку в начале параграфа. Если диапазон окажется неверным, то span не будет работать.

В Android параграфами считается часть текста, отделённая символами перевода строки (\n).

Написание своих span’ов

При написании собственных span’ов надо определиться, что будет затрагивать span, чтобы выбрать, от какого класса надо наследоваться:

  • Затрагивает текст на уровне символов → CharacterStyle
  • Затрагивает текст на уровне параграфа → ParagraphStyle
  • Затрагивает вид текста → UpdateAppearance
  • Затрагивает размер текста → UpdateLayout

Вот пример span’а для смены шрифта:

class CustomTypefaceSpan(private val font: Typeface?) : MetricAffectingSpan() }
}

Представим, что мы хотим сделать свой собственный span для выделения блоков кода, для этого отредактируем наш предыдущий span – добавим после установки шрифта еще и изменение цвета фона текста:

class CodeBlockSpan(private val font: Typeface?) : MetricAffectingSpan() { … fun update(textPaint: TextPaint) { textPaint.apply { // Устанавливаем новый шрифт … bgColor = lightGray // Устанавливаем цвет фона } }
}

Применим span к тексту:

// Устанавливаем один кастомный span
spannable.setSpan(CodeBlockSpan(typeface), ...)

Но точно такой же результат можно получить, скомбинировав два span’а: возьмем наш предыдущий CustomTypefaceSpan и BackgroundColorSpan из Android фреймворка:

// Устанавливаем цвет фона
spannable.setSpan(BackgroundColorSpan(lightGray), ...) // Устанавливаем шрифт
spannable.setSpan(CustomTypefaceSpan(typeface), ...)

Дело в том, что самописные span’ы не могут реализовывать интерфейс Parcelable, в отличие от системных. Эти два решения будут иметь отличие.

При использовании span’ов из фреймворка разметка останется. При передаче стилизованной строки через Intent или буфер обмена в случае самописного span’а разметка не сохранится.

Использование span’ов в тексте

Для стилизованного текста во фреймворке есть два интерфейса: Spanned и Spannable (с неизменяемой и изменяемой разметкой соответственно) и три реализации: SpannedString (неизменяемый текст), SpannableString (неизменяемый текст) и SpannableStringBuilder (изменяемый текст).

SpannableStringBuilder, например, используется внутри EditText, которому требуется изменять текст.

Добавить новый span к строке можно при помощи метода:

setSpan(Object what, int start, int end, int flags)

И последним параметром можно управлять, какое будет поведение span’а при вставке нового текста: будет ли span распространяться на текст, вставленный в начальную или конечную точки (если в середину вставить новый текст, то span автоматически применится к нему вне зависимости от значений флага). Через первый параметр передается span, затем указывается диапазон индексов в тексте.

Перечисленные выше классы различаются не только семантически, но и тем, как они устроены внутри: SpannedString и SpannableString используют массивы для хранения span’ов, а SpannableStringBuilder использует дерево интервалов.

Таким образом, если стоит задача применить стиль к какому-то тексту, то при выборе класса надо руководствоваться семантическими требованиями: будут ли строка и стили изменяемыми. Если провести тесты на скорость отрисовки текста в зависимости от количества span'ов, то будут такие результаты: при использовании в строке до ~250 span’ов SpannableString и SpannableStringBuilder работают примерно с одинаковой скоростью, но если элементов разметки становится больше 250, то SpannableString начинает проигрывать. Но если для разметки требуется больше 250 span’ов, то предпочтение надо всегда отдавать SpannableStringBuilder.

Проверка на наличие span’а в тексте

И на Stackoverflow можно встретить такой код: Периодически возникает задача проверить, есть ли в spanned строке определенный span.

fun <T> hasSpan(spanned: Spanned, clazz: Class<T>): Boolean { val spans: Array<out T> = spanned.getSpans(0, spanned.length, clazz) return spans.isNotEmpty()
}

Такое решение будет работать, но оно неэффективно: придется пройти по всем span’ам, проверить, относится ли каждый из них к переданному типу, собрать результат в массив и в конце всего лишь проверить, что массив не пустой.

Более эффективным решением будет использование метода nextSpanTransition():

fun <T> hasSpan(spanned: Spanned, clazz: Class<T>): Boolean { val limit = spanned.length return spanned.nextSpanTransition(0, limit, clazz) < limit
}

Разметка текста в различных языковых ресурсах

Например, нам надо выделить слово “text” в английской версии и “texto” в испанской: Может возникнуть такая задача, когда требуется выделить при помощи разметки определенное слово в различных строковых ресурсах.

<!-- values-en/strings.xml -->
<string name="title">Best practices for text in Android</string> <!-- values-es/strings.xml -->
<string name=”title”>Texto en Android: mejores prácticas</string>

В UI надо будет просто установить строковый ресурс в TextView: Если требуется что-то простое, например, выделить слово жирным, то можно использовать обычные html-теги (<b>).

textView.setText(R.string.title)

Решением будет использовать специальный тег <annotation>. Но если требуется что-то более сложное, например, смена шрифта, то html уже не получится использовать. Когда мы вытащим строку из ресурсов, эти теги автоматически сконвертируются в span’ы Annotation, расставленными по тексту с соответствующими ключами и значениями. Этот тег позволяет определить любую пару ключ-значение в xml-файле. После этого можно распарсить список аннотаций в тексте и применить нужные span’ы.

Предположим, нам надо поменять шрифт при помощи CustomTypefaceSpan.

Добавим тег и определим для него ключ “font” и значение – тип шрифта, который мы хотим использовать – “title_emphasis”:

<!-- values-en/strings.xml -->
<string name="title">Best practices for <annotation font=”title_emphasis”>text</annotation> in Android</string> <!-- values-es/strings.xml -->
<string name=”title”><annotation font=”title_emphasis”>Texto</annotation> en Android: mejores prácticas</string>

Вытащим строку из ресурсов, найдем аннотации с ключом “font” и расставим span’ы:

// Вытаскиваем из ресурсов текст как SpannedString, чтобы найти в нем span’ы
val titleText = getText(R.string.title) as SpannedString // Получаем все аннотации
val annotations = titleText.getSpans(0, titleText.length, Annotation::class.java) // Делаем копию строки как SpannableString
// теперь можно менять разметку текста
val spannableString = SpannableString(titleText) // проходим по всем аннотациям
for (annotation in annotations) { // находим аннотации с ключом "font" if (annotation.key == "font") { val fontName = annotation.value // проверяем значение аннотации, чтобы установить нужный шрифт if (fontName == "title_emphasis") { val typeface = getFontCompat(R.font.permanent_marker) // устанавливаем span в тот же диапазон, что и аннотации spannableString.setSpan( CustomTypefaceSpan(typeface), titleText.getSpanStart(annotation), titleText.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } }
} styledText.text = spannableString

Но это не относится к аннотациям, которые имплементируют Parcelable. Выше упоминалось, что span’ы не из Android-фреймворка не могут имплементировать Parcelable и передаваться через Intent. Так что аннотированную строку можно передать через Intent и распарсить точно таким же образом, расставив свои span’ы.

Также можно задавать различные отступы перед текстом. TextView умеет отображать не только текст, но и картинки. Это абстрактный класс, который имеет три реализации, напрямую с ними обычно не приходится работать, если не писать свой элемент управления: Под капотом это работает так, что TextView создает дочерний класс Layout, ответственный непосредственно за отображение текста.

  • BoringLayout используется для простых текстов, не поддерживает переносы строк, RTL и другие вещи, но при этом является самым легковесным. TextView использует его, если текст удовлетворяет всем ограничениям.
  • StaticLayout используется в TextView для остальных случаев.
  • DynamicLayout используется для изменяемого текста в EditText.

(подробнее можно посмотреть в документации) У Layout есть много методов, которые позволяют узнать различные параметры отображаемого текста: координаты строк, baseline, координаты начала и конца текста в строке и т.д.

Например, некоторые разработчики сталкиваются с задачей выделения части текста в прямоугольники с закругленными углами, и пытаются искать ее решение через span’ы, которые не применимы в решении этой проблемы. Такие методы могут быть очень полезны.

Вот примерное решение: Зато на помощь могут прийти методы класса Layout.

При помощи аннотаций выделяем слова, которые должны быть обведены в прямоугольники.

Затем создаем 4 drawable ресурса для всех случаев переноса текста, который должен быть заключен в прямоугольники:

Теперь у нас есть индексы начала и конца такой аннотации. Далее находим нужные нам аннотации в тексте, как это описывалось выше. Через методы Layout можно узнать номер строки, на которой начинается проаннотированный текст, и на которой заканчивается:

val startLine = layout.getLineForOffset(spanStartIndex)
val endLine = layout.getLineForOffset(spanEndIndex)

Рассмотрим простой случай, когда проаннотированная часть текста оказалась на одной строке, тогда нам понадобится всего один прямоугольник с четырьмя закругленными углами. Далее придется нарисовать один или несколько прямоугольников. Определим его координаты и нарисуем:

... if (startLine == endLine) { val lineTop = layout.getLineTop(startLine) // координаты верха строки val lineBottom = layout.getLineBottom(startLine) // координаты низа строки val startCoor = layout.getPrimaryHorizontal(spanStartIndex).toInt() // координаты начала прямоугольника val endCoor = layout.getPrimaryHorizontal(spanEndIndex).toInt() // координаты конца прямоугольника // Рисуем прямоугольник drawable.setBounds(startCoor, lineTop, endCoor, lineBottom) drawable.draw(canvas) ...

Как видно из этого примера, Layout хранит очень много полезной информации по отображаемому тексту, которая может помочь в реализации разных нестандартных задач.

При этом onMeasure() занимает больше всего времени, в отличие от двух других методов: в этот момент пересоздается класс Layout и производится расчет размеров текста. TextView, как и любая View, при отображении проходит через три фазы: onMeasure(), onLayout() и onDraw(). Изменение же цвета текста будет более легковесным, потому что потребует только вызова onDraw(). Так что изменение размера текста (например, смена шрифта) влечет за собой много работы. Если слово уже есть в кэше, то повторный вызов onMeasure() для него займет 11-16% от времени, которое потребовалось бы для полного расчета. Как упоминалось выше, в системе есть глобальный кэш слов с рассчитанными размерами.

Ускорение показа текста

Идея была в том, чтобы виртуально рисовать текст до показа его на экране, таким образом “разогрев” системный кэш. В 2015 году разработчики Instagram ускорили показ комментариев к фотографиям, используя глобальный кэш. Когда подходила очередь показа текста, пользователь видел его гораздо быстрее, так как текст уже был измерен и лежал в кэше.

С использованием нового API в фоновом потоке будет выполнено 90% работы. Начиная с Android P (28) разработчики Google добавили в API возможность выполнить фазу измерения размера текста заранее в фоновом потоке – PrecomputedText (и бэкпорт для API начиная с Android I (14)PrecomputedTextCompat).

Пример:

// UI thread val params: PrecomputedText.Params = textView.getTextMetricsParams()
val ref = WeakReference(textView) executor.execute { // background thread val text = PrecomputedText.create("Hello", params) val textView = ref.get() textView?.post { // UI thread val textView = ref.get() textView?.text = text }
}

Показ большого текста

Иначе приложение может перестать плавно работать или вовсе начать зависать, так как будет делать много работы на главном потоке, чтобы показать огромный текст, который пользователь, возможно, даже и не прокрутит до конца. Если надо показать большой текст, то не стоит его сразу передавать в TextView. Для еще большего ускорения можно заранее рассчитывать размер блоков текста, используя PrecomputedText. Решением будет разбиение текста на части (например, параграфы) и показ отдельных частей в RecyclerView.

Для облегчения встраивания PrecomputedText в RecyclerView разработчики Google сделали специальные методы PrecomputedTextCompat.getTextFuture() и AppCompatTextView.setTextFuture():

fun onBindViewHolder(vh: ViewHolder, position: Int) { val data = getData(position) vh.textView.setTextSize(...) vh.textView.setFontVariationSettings(...) // запускаем расчет заранее val future = PrecomputedTextCompat.getTextFuture( data.text, vh.textView.getTextMetricsParamsCompat(), myExecutor ) // передадим future в TextView, который будет ждать результат до onMeasure() vh.textView.setTextFuture(future)
}

Так как RecyclerView во время скролла создает новые элементы, которые еще не видны пользователю, то такое решение будет иметь достаточно времени для выполнения работы в фоне до того, как элемент будет показан пользователю.

Следует помнить, что после вызова метода getTextFuture() нельзя менять стиль текста (например, поставить новый шрифт), в противном случае произойдет исключение, так как значения, с которыми вызывался getTextFuture(), не будут совпадать с теми, которые окажутся в TextView.

Что нужно знать, когда устанавливаешь текст в TextView

При вызове метода TextView.setText() на самом деле внутри создается копия строки:

if (type == SPANNABLE || movementMethod != null) { text = spannableFactory.newSpannable(spannable) // Копирование
} else { text = new SpannedString(spannable) // Копирование
}

То есть если установить текст со span’ами в TextView, а затем попытаться изменить переданный в setText() объект, то в отображении ничего не произойдет.

В TextView имеется возможность заменить фабрику, используемую по-умолчанию, на свою реализацию. Как видно из кода, новый объект создается при помощи фабрики. Для этого пишем фабрику, возвращающую тот же объект, и устанавливаем ее в TextView через сеттер spannableFactory: Это может быть полезно, чтобы не делать лишних копирований строк.

class MySpannableFactory : Spannable.Factory() { override fun newSpannable(source: CharSequence): Spannable { return source as? Spannable ?: super.newSpannable(source) }
} textView.spannableFactory = MySpannableFactory()

SPANNABLE), чтобы происходило обращение к нашей фабрике. При установке текста надо не забывать делать вызов textView.setText(spannable, BufferType.

Разработчики Google советуют использовать это решение для отображения текста со span’ами в RecyclerView, чтобы уменьшить потребление ресурсов нашим приложением.

Надо просто взять текст из TextView и добавить в него новый span. Если текст уже установлен в TextView, и надо добавить новый span, то совсем не нужно делать повторный вызов setText(). TextView автоматически слушает spannable-строку на добавление новых span’ов, и перерисовывается:

val spannable = textView.getText() as Spannable
val span = CustomTypefaceSpan(span) spannable.setSpan(span, ...)

Если новое изменение не затрагивает размер текста, достаточно вызвать invalidate(), в противном случае – requestLayout(): Если же у нас есть span, который стоит в тексте у TextView, то можно обновить значения его параметров и заставить TextView перерисоваться.

val spannable = textView.getText() as Spannable
val span = CustomTypefaceSpan(span) spannable.setSpan(span, ...) span.setTypeface(anotherTypeface) textView.requestLayout() // re-measure and re-draw // or textView.invalidate() // re-draw

Для ее включения достаточно указать в разметке атрибут autoLink. В TextView есть возможность автоматического обнаружения ссылок. Вот примерный код, как это происходит в SDK при вызове setText(): При значении autoLink=”web” TextView во время установки нового текста найдет в нем все URL через регулярное выражение и установит на найденные диапазоны символов URLSpan.

spannable = new SpannableString(string);
Matcher m = pattern.matcher(text); while (...) { // проходимся по всем найденным ссылкам String utl = … URLSpan span = new URLSpan(url); spannable.setSpan(span, ...);
}

В таком случае лучше вынести определение ссылок в фоновый поток. Так как это происходит на UI потоке, то не стоит использовать autoLink=”web” внутри элементов RecyclerView. И здесь на помощь нам приходит класс LinkifyCompat:

// Вытаскиваем ссылки, когда подготавливаем данные к показу на background thread val spannable = SpannableString(string)
LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS) // В адаптере RecyclerView
override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.textView.setText(spannable, BufferType.SPANNABLE) // ...
}

Эту возможность лучше вообще никогда не использовать. У autoLink еще есть возможность указать значение map – распознавание почтовых адресов (оно же будет включено при значении all). В исходном коде SDK в методе Linkify.gatherMapLinks() можно увидеть такую строку, этот код выполняется на главном потоке: Проблема в том, что под капотом там будет создание экземпляра WebView, через который будет осуществляться поиск адреса!

while ((address = WebView.findAddress(string)) != null) { ...
}

А внутри WebView стоит TODO от разработчиков SDK:

public static String findAddress(String addr) { // TODO: Rewrite this in Java so it is not needed to start up chromium // Could also be deprecated return getFactory().getStatics().findAddress(addr);
}

Решением будет новая технология Smart Linkify, к сожалению доступная только начиная с Android P (28), которая работает на основе нейронных сетей и распознает различную информацию, в том числе и почтовые адреса. Но что же тогда использовать? Вот небольшой пример использования:

// UI thread val text: Spannable = …
val request = TextLinks.Request.Builder(text)
val ref = WeakReference(textView) executor.execute { // background thread TextClassifier.generateLinks(request).apply(text) val textView = ref.get() textView?.post { // UI thread val textView = ref.get() textView?.text = text }
}

Над адресами при нажатии будет отображаться контекстный toolbar с возможными действиями, например показ адреса на Google карте. В отличие старого Linkify, распознанные адреса не будут простыми ссылками.

Технология Smart Linkify способна распознавать различные данные: номера телефонов, авиарейсы и многое другое.

С его помощью пользователю гораздо проще установить курсор на нужную позицию. Начиная с Android P (28), появился новый элемент управления – Magnifier, который показывает увеличенные символы при выделении текста.

По умолчанию он работает в TextView, EditText и WebView, но при желании его можно использовать при написании своих элементов пользовательского интерфейса: его API достаточно прост.

В данной статье были опущены многие нововведения последних версий Android и смежные темы, заслуживающие отдельных статей, например:

  • работа со стилями и темами при отображении текста
  • работа со шрифтами
  • работа с классами, производными от TextView (например, EditText)

Если кому-то интересна одна из этих тем, рекомендую посмотреть презентацию с прошедшего Google I/O'19 “Best Practices for Using Text in Android”.

Статьи

Доклады

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть