Formatted text in android

Getting Started with Spannables on Android

Begin formatting text like a pro!

T ext, probably the most basic element you get to work with on Android, and sometimes we have to format certain parts of our text. Formatting helps place emphasis on text, indicating that the formatted parts of text mean more when compared to plain parts. Formatting options include underline, bold, italics, strikethrough and many more. Android provides a number of options to its’ developers when it comes to formatting text. A direct approach is by using HTML markup to define sections of our text to be formatted, tags like the — underline, — bold and — italics do just that. Android properly helps us convert the markup provided into nicely formatted text.

Using XML

Or Code

We have an awesome option for formatting, don’t we? but that’s not all there is to formatting.

Formatting is the arrangement of a material in a particular layout, shape, size and general makeup — Merriam-Webster

Formatting goes beyond boldening or underlining text, we have a pool of design possibilities we can replicate. For example, we might want to set the colour of a fraction of our text differently or its size or its background, whatever. These designs are achievable using spannables without having to worry about too much.

Our First Spannable

To get started, create a new android studio project with your preferred project name and add this to the activity_main.xml . In the code below, we have a parent LinearLayout and five textviews. Android treats spannables like basic text so we can easily set the formatted text to the textviews, hence the usage of textviews.

In the layout above, we created five TextView s to hold differently formatted text, we’d go through them one after the other, starting with the bold format. But before that, let’s take a moment to understand the general structure of how spannables are set up and how the formatting takes place.

How are spannables set up?

When it comes to using Spannables, we can create them in three ways. By using any of the SpannedString, SpannableString and the SpannableStringBuilder classes. Each of these classes has features that differentiate them, the differences are outlined below.

  1. SpannedString — This class does not allow modifications to the text set into the Spannable. No modifications to the text and no modifications to the design (the spans). In other words, it’s immutable.
  2. SpannableString — This class allows for modifications of the design/styles added to the text, but no direct modification of the text used by the Spannable.
  3. SpannableStringBuilder — This class allows for modifications to the text itself and its designs.

The key takeaway here can be summarised as such:

Use SpannedString when you want to set the text and design once without any further modifications, SpannableString when you won’t be making any changes to the text but to the design and SpannableStringBuilder when you’d be making changes to both the underlying text and the design.

Format For Spannables

Now that we have a clear view of the types of spans we can use, let’s dig deeper into their general structure. Spannables you’d use in the future generally come in this fashion:

Читайте также:  Как отключить подписку литрес андроид

Our spannable object provides a setSpan method we can use to apply different span styles to sections of our text. It takes in four arguments, let’s dive in!

  1. what — This refers to the actual span style we would be applying, think of it as an object that instructs our spannable to apply a certain style to the text.
  2. startIndex — Remember our spannables deal with text? Right. The startIndex parameter denotes the index where the text to be formatted begins. Say the text to be formatted is thyself in steady thyself , we could deduce that the startIndex for this text is 7.
  3. endIndex — This does pretty much the opposite of what the startIndex does, it denotes the index of the character that ends the text to be formatted. Using our example from our startIndex explanation, our endIndex is the index of the letter f in thyself , so that would make it 13.
  4. flags — This indicates how the text provided to the spannables should be treated, we have various values that can be assigned to this parameter position, examples are SPAN_EXCLUSIVE_EXCLUSIVE , SPAN_EXCLUSIVE_INCLUSIVE , SPAN_INCLUSIVE_INCLUSIVE and many more. A detailed explanation is provided below.

Spannable Flags

As explained above, spannable flags indicate how the Spanning process should treat the text to be spanned. Some of the flags we can use are explained below.

  1. SPAN_INCLUSIVE_INCLUSIVE: This allows new text to be added to both the starting and end points of the text.
  2. SPAN_INCLUSIVE_EXCLUSIVE: This allows new text to be added to the starting point, but not to the end.
  3. SPAN_EXCLUSIVE_INCLUSIVE: This does not allow text to be added to the starting point, but the end.
  4. SPAN_EXCLUSIVE_EXCLUSIVE: This does not allow text to be added to the start and end points of the text.

You can read about other span flags here, since we have covered the general structure of applying spans, and dug deep into it’s required parameters, this seems like a good time to write our first span.

The Bold Span

Let’s start by retrieving a reference to a TextView in our layout, and then setting up a function to apply the spanning. We do that by writing:

Источник

Текст с разметкой в android.widget.TextView

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

Читайте также:  Android sdk platform tool

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

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

Я реализовал весь, необходимый мне функционал и выяснил ещё несколько довольно интересных вещей, столкнувшись с некоторым количеством подводных камней (впрочем, не очень острых). Можно сказать, всё нижеописанное — руководство по созданию достаточно мощной справочной системы в своём приложении практически даром.

Задачи

В данном примере мы создадим приложение с двумя Activity, одна из которых содержит TextView, исполняющий роль браузера, из которого, в частности, можно вызвать вторую Activity, демонстрирующую работу с параметрами вызова. Мы выясним, каким образом можно создавать страницы текста с разметкой и изображениями и связывать их ссылками.

Содержимое страниц берётся из строк в ресурсах приложения, а изображения являются drawable-ресурсами. Небольшие изменения в коде позволят использовать другие расположения.

Создание приложения

Любым удобным нам способом создаём обычное приложение:

Немного пояснений к манифесту. Если с первой Activity всё понятно, вторая (AnotherActivity) содержит некие дополнительные описатели.

android:exported=«false» необходимо для того, чтобы компилятор не выдавал предупреждения о том, что мы забыли что-то прописать в экспортируемом компоненте. На мой взгляд, чисто декоративный момент, но чем меньше жёлтых треугольничков — тем спокойнее.

Раздел intent-filter содержит описатели того, каким образом и при каких обстоятельствах будет происходить запуск Activity.

означает, что можно запустить Activity ссылкой вида activity-run://AnotherActivityHost?params.

Значения action и category необходимы системе для того чтобы обнаружить и запустить Activity.

Подготовка ресурсов

Строки, содержащие разметку, должны иметь аттрибут formatted со значением false, а содержимое должно передаваться в блоке CDATA, чтобы у компилятора не было претензий к разметке и специальным символам. В данном примере признаком статьи будет префикс article_ в названии строки.

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

Изображения могут быть формата jpg, png или gif без анимации. Анимированный gif отображается статичной картинкой. Расположение стандартное для ресурсов, для дисплеев разной плотности можно подготовить свой вариант картинки. В данном примере все изображения находятся в drawable-nodpi

Как всё работает

Рассмотрим некоторые части кода подробно.

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

tvContent.setLinksClickable(true); указывает на то, что ссылки в данном элементе реагируют на нажатие.

tvContent.setMovementMethod(new LinkMovementMethod()); назначает способ навигации по элементу. Использованный нами LinkMovementMethod интересен сам по себе и, возможно, заслуживает отдельной статьи. Я лишь скажу, что при необходимости более полного контроля можно создать его наследника, переопределенные методы которого позволят отслеживать все действия со ссылками в элементе.

В данном методе происходит получение строки по идентификатору из строковых ресурсов, её преобразование из HTML в специальный объект Spanned, затем ещё одно преобразование в Spannable и установка в TextView в качестве содержимого. Всё это кажется довольно громоздким, но тому есть причины.

В TextView, на мой взгляд, странный порядок обработки спанов — с конца списка. При естественном расположении спанов после преобразования строки из HTML, изменения внешнего вида вложенных спанов перекрываются свойствами спанов, их содержащих. Для нормального отображения приходится буквально выворачивать маркировку наизнанку с помощью метода revertSpanned:

Читайте также:  Узнать что расходует батарею android

Определение обработчика ссылок на изображения минималистично и призвано загружать только картинки из ресурсов. Поскольку мы рассматриваем вариант справочной системы, я посчитал, что этого будет достаточно. С вашего позволения, я не буду цитировать его. Если вы хотите большего, можно обратиться, например, к данной статье.

Более интересен нам будет Html.TagHadler:

Здесь у нас происходит несколько интересных вещей.

При преобразовании из HTML в Spanned методом Html.fromHtml, обрабатываются тэги br , p , div , em , b , strong , cite , dfn , i , big , small , font , blockquote , tt , a , u , sup , sub , h1. h6 и img . В случае, если тэг не опознан, вызывается Html.TagHandler (если, конечно, он передан в вызов).

Мы проверяем, не является ли переданный тэг «нашим» и если это так, создаём соответствующий Span — элемент разметки, а затем накладываем его на текст. Я создал несколько собственных Span-ов, они будут рассмотрены далее. Как правило, Span-ы наследуются от android.text.style.CharacterStyle.

К сожалению, у меня не получилось малой кровью добиться центрования отдельных строк или абзацев, а встроенной возможности для этого не существует. Также, нельзя прочесть атрибуты тэга из xmlReader, поскольку он реализован не полностью. По этой причине пришлось изобретать свой способ передачи параметров: значение является частью тега. В нашем примере таким образом передаётся значение цвета в тэге color, преобразовываемом в ParameterizedSpan. Получается что-то вроде красный . Это достаточно ограниченный и не очень удобный способ, но иногда лучше такой, чем никакого.

Этот код делает следующее: В случае, если передан открывающий Span, он добавляется к концу строки в текущем её виде. В случае, если Span закрывающий, мы находим в строке его открывающий аналог, запоминаем его положение, затем удаляем и добавляем новый, но уже с информацией о начальном положении и длине.

Мы завершили рассмотрение класса Activity, являющегося основным модулем нашего приложения. Теперь рассмотрим вспомогательные классы.

Это Span общего назначения и с его помощью можно задать большинство параметров стиля текста. Его можно использовать как базу для создания стилей текста из собственных тэгов.

Этот класс описывает элемент, который по нажатию на него обеспечивает переход к статье, чей идентификатор является его параметром. Здесь я применил производное от способа, описанного мной ранее: сам тэг является собственным параметром, а его класс определяется префиксом article_. Поднимемся выше, к описанию Html.TagHandler:

Обработчик тэгов, увидев тэг, начинающийся на article_, создаёт ArticleSpan, задавая ему в качестве параметра название тэга. Элемент, при нажатии на него, вызывает метод MainActivity.setArticle, после чего в TextView устанавливается новый текст.

Здесь реализован элемент, получающий параметр явно и отдельно от своего имени. Претензия на своего рода стандарт именования тэгов, раз уж нельзя передавать атрибуты.

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

Вызов Activity

В HTML мы видим следующее:

При нажатии на ссылку, происходит вызов AnotherActivity с передачей параметров в Intent. Эти параметры можно получить и использовать:

Использованные материалы

Следующие материалы очень ускорили создание данной статьи, да и, чего уж там, сделали его вообще возможным:

Я очень рад, что существует на свете StackOverflow.com.

Архив с исходниками проекта

Архив с исходными текстами и ресурсами проекта можно взять здесь.

Источник

Оцените статью