- Getting Started with Spannables on Android
- Begin formatting text like a pro!
- Using XML
- Or Code
- We have an awesome option for formatting, don’t we? but that’s not all there is to formatting.
- Our First Spannable
- How are spannables set up?
- Format For Spannables
- Spannable Flags
- The Bold Span
- Использование RichText в Android. Spannable
- Теория
- Практика
- Вместо заключения
- Underspanding spans
- Spantastic text styling with Spans
- To style text in Android, use spans! Change the color of a few characters, make them clickable, scale the size of the…
- Under the hood: how spans work
- Setting text for maximum performance
- 1. Text set on a TextView never changes
- 2. Text style changes by adding/removing spans
- 3. Text changes (reusing TextView)
- Bonus performance tip
- Under the hood: passing text with spans intra and inter-process
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.
- 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.
- 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.
- 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!
- 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.
- 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.
- 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.
- 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.
- SPAN_INCLUSIVE_INCLUSIVE: This allows new text to be added to both the starting and end points of the text.
- SPAN_INCLUSIVE_EXCLUSIVE: This allows new text to be added to the starting point, but not to the end.
- SPAN_EXCLUSIVE_INCLUSIVE: This does not allow text to be added to the starting point, but the end.
- 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:
Источник
Использование RichText в Android. Spannable
Привет Хабраобщество! Эта статья об использовании Spannable в Android. Предназначен он для стилизации текста. Если Вас интересует как реализовать нечто подобное:
тогда добро пожаловать под кат. Статья ориентированная на разработчиков, у которых нет большого опыта разработки под Android.
Теория
Spannable — интерфейс, который описывает маркировку обычного текста некими объектами. Задание этих объектов заключается в присвоению части текста некоторого определенного стиля. Такими маркировочными объектами могут быть экземпляры классов, которые реализуют интерфейс ParcelableSpan. Добавление маркировки осуществляется методом:
Удаление, соответственно, методом:
Теории немного, перейдем сразу к практике.
Практика
Для освоения практики необходимо создать android проект или открыть уже существующий. Я создал чистый проект, на главную activity разместил один TextView. Идем в метод, где будем инициализировать наш TextView (у меня onCreate) и добавляем следующий текст:
Итак, разберемся что же мы написали. SpannableString — класс, который реализует интерфейс Spannable (с другими реализациями можно ознакомится на сайте официальной документации). UnderlineSpan — реализация ParcelableSpan, маркирует часть текста как подчеркнутый (в нашем примере это с 8-ой по 17-тую букву). Флаг Spanned.SPAN_EXCLUSIVE_EXCLUSIVE обозначает, что наш span не будет расширятся на вставки текста слева или справа от маркированной части. (Со всеми флагами можно ознакомится на официальном сайте, при работе с текстами readonly они не столь важны).
Также здесь ми использовали еще одну реализацию ParcelableSpan — StyleSpan. Как вы уже догадались, с его помощью можно маркировать текст как полужирный (Typeface.BOLD) или курсив (Typeface.ITALIC).
С кодом ми разобрались, запускаем наш проект и смотрим на результат. Текст должен выглядеть следующим образом:
Хорошо, идем дальше: добавим на наше activity кнопку, а в инициализирующий метод следующий текст:
На этот раз не будем разбирать код, ничего нового уже для нас нет. Мы использовали маркировку курсивом, которую я описал выше. Запускаем, смотрим.
Мы смогли вывести стилизованный текст на кнопку.
Пойдем еще дальше, реализуем обработчик события клика кнопки, в нем пишем следующий код:
Здесь у нас появляется новый класс — ForegroundColorSpan, который задает цвет нашему тексту, в примере мы задали зеленой. Мы его используем в паре с StyleSpan. Запускаем приложение, смотрим на результат.
Мы смогли запихнуть стилизованный текст даже в Toast. Также мы показали, что для одной части текста можно использовать несколько маркировок со стилями.
Вместо заключения
В примере мы использовали только некоторые из доступных стилей, больший список можно посмотреть на сайте разработчиков. Думаю вы сможете поэкспериментировать с ними.
Целью статьи было в доступной форме показать столь сильный и простой инструмент для стилизации своего приложения разработчикам, которые не знали о его существовании или просто не использовали его. Надеюсь что статья будет полезна.
Источник
Underspanding spans
Spans are powerful concepts that allow styling text at character or paragraph levels by providing access to components like TextPaint and Canvas. We talked about how to use Spans, what Spans are provided out of the box, how to easily create your own, and how to test them in a previous article.
Spantastic text styling with Spans
To style text in Android, use spans! Change the color of a few characters, make them clickable, scale the size of the…
Let’s see what APIs can be used to ensure maximum performance for specific use cases when working with text. We’re going to explore more of what’s going on under the hood with spans and how the framework uses them. In the end, we’ll see how we can pass spans in the same process or in-between processes and, based on that, what kind of caveats you need to be aware of when deciding to create your own custom spans.
Under the hood: how spans work
The Android framework handles text styling and work with spans in several classes: TextView , EditText , the layout classes ( Layout , StaticLayout , DynamicLayout ) and TextLine (a package private class used in Layout ) and it depends on several parameters:
- Type of text: selectable, editable or non-selectable
- BufferType
- TextView ’s LayoutParams type
- etc
The framework checks whether the Spanned objects contain instances of different framework spans and triggers different actions.
The logic behind text layout and draw is complicated and spread throughout different classes; in this section we can present only a simplistic view of how text is handled and only for some cases.
Every time a span is changed, the TextView.spanChange checks whether the span is an instance of UpdateAppearance , ParagraphStyle or CharacterStyle and, if yes, invalidates itself, triggering a new drawing of the view.
The TextLine class represents a line of styled text and it works specifically with spans that extend CharacterStyle , MetricAffectingSpan and ReplacementSpan . This is the class that triggers MetricAffectingSpan.updateMeasureState and CharacterStyle.updateDrawState .
The base class that manages text layout in visual elements on the screen is android.text.Layout . Layout , along with two of its subclasses, StaticLayout and DynamicLayout , check the spans set on text to compute the line height and the layout margin. Apart from this, whenever a span displayed in a DynamicLayout is updated, the layout checks whether the span is a UpdateLayout span and generates a new layout for the affected text.
Setting text for maximum performance
There are several memory-efficient ways of setting text in a TextView , depending on your needs.
1. Text set on a TextView never changes
If you just set the text on a TextView once and never update it, you can just create a new instance of SpannableString or SpannableStringBuilder , set the needed spans and then call textView.setText(spannable) . Since you’re not working with the text anymore, there’s no more performance to be improved.
2. Text style changes by adding/removing spans
Let’s consider the case where the text doesn’t change, but the spans attached to it do. For example, let’s say that whenever a button is clicked, you want a word from the text to become grey. So, we need to add a new span to the text. To do this, most likely you’ll be tempted to call textView.setText(CharSequence) two times: first to set the initial text and then again when the button is clicked. A more optimal solution would be to call textView.setText(CharSequence, BufferType) and just update the spans of a Spannable object when the button is clicked.
Here’s what’s going on under the hood with each of these options:
When calling textView.setText(CharSequence) , under the hood TextView creates a copy of your Spannable as a SpannedString and keeps it in memory as a CharSequence . The consequence of this is that your text and the spans are immutable. So, when you need to update the text style, you will have to create a new Spannable , with text and spans, call textView.setText again, which, in turn, will create a new copy of the object.
When calling textView.setText(CharSequence, BufferType) , the BufferType parameter tells the TextView what type of text is set: static (the default type when calling textView.setText(CharSequence) ), styleable / spannable text or editable (which would be used by EditText ).
Since we’re working with text that can be styled, we can call:
In this case, TextView doesn’t create a SpannedString anymore, but it will create a SpannableString , with the help of a member object of type Spannable.Factory . Therefore now, the CharSequence copy kept by the TextView has mutable markup and immutable text.
To update the span we first get the text as Spannable and then update the spans as needed.
With this option, we only create the initial Spannable object. TextView will hold a copy of it, but when we need to modify it, we don’t need to create any other objects because we will be working directly with the instance of the Spannable text kept by TextView . However, TextView will be informed only about the adding/removing/repositioning of the spans. If you change an internal attribute of the span, you’ll have to call either invalidate() or requestLayout() , depending on the type of the change. See details in the “ Bonus performance tip” below.
3. Text changes (reusing TextView)
Let’s say that we want to reuse a TextView and set the text multiple times, like in a RecyclerView.ViewHolder . By default, independent of the BufferType set, TextView creates a copy of the CharSequence object and holds it in memory. This ensures that all TextView updates are deliberate, and not by accident, when the developer changes the CharSequence value for other reasons.
In the Option 2 above, we saw that when setting the text via textView.setText(spannableObject, BufferType.SPANNABLE) , the TextView copies the CharSequence by creating a new SpannableString using a Spannable.Factory instance. So every time we set a new text, it will create a new object. If you’d like to take more control over this process and avoid the extra object creation implement your own Spannable.Factory , override newSpannable(CharSequence), and set the factory to the TextView .
In our own implementation, we want to avoid that new object creation, so we can just return the CharSequence cast as a Spannable . Keep in mind that in order to do this, you have to call textView.setText(spannableObject, BufferType.SPANNABLE) otherwise, the source CharSequence will be an instance of Spanned which cannot be cast to Spannable , resulting in a ClassCastException .
Set the Spannable.Factory object once right after you get a reference to your TextView . If you’re using a RecyclerView , do this when you first inflate your views.
With this, you’re avoiding extra object creation every time your RecyclerView binds a new item to your ViewHolder .
To get even more performance when working with text and RecyclerViews , instead of creating your Spannable object from the String in the ViewHolder , do that before you pass your list to the Adapter . This allows you to construct the Spannable objects on a background thread, together with any other work you do with your list elements. Your Adapter can then keep a reference to a List .
Bonus performance tip
If you only need to change an internal attribute of a span (for example, the bullet color for a custom bullet span), you don’t need to call TextView.setText again, but just invalidate() or requestLayout() . Calling setText again would lead to unnecessary logic being triggered and objects being created, when the view needs to just either redraw or remeasure.
What you need to do is keep a reference to your mutable span and, depending on what kind of property you changed in the view, call:
- TextView.invalidate() if you’re just changing text appearance, to trigger a redraw and skip redoing layout.
- TextView.requestLayout() if you made a change that affects the size of the text, so the view can can take care of measuring, laying out and drawing.
Let’s say that you have your custom bullet point implementation, where the default bullet point color is red. Whenever you press a button, you want to change the color of the bullet point to grey. The implementation would look like this:
Under the hood: passing text with spans intra and inter-process
Custom span attributes will not be used when Spanned objects are passed intra or inter-process. If the desired styling can be achieved just with the framework spans, prefer applying multiple framework spans to implementing your own spans. Otherwise, prefer implementing custom spans that extend some of the base interfaces or abstract classes.
In Android, text can be passed in the same process (intra-process), for example from one Activity to another via Intents, and between processes (inter-process) when text is copied from one app to another.
Custom span implementations can’t pass across process boundaries, since other processes don’t know about them and wouldn’t know how to handle them. Android framework spans are global objects but only the spans that extend from ParcelableSpan can be passed intra and inter process. This functionality allows parceling and un-parcelling of all the properties of the span defined in the framework. TextUtils.writeToParcel method is in charge of saving the spans information in the Parcel .
For example, you can pass Spans in the same process, between Activities via an intent:
So, even if you’re passing spans in the same process, only framework ParcelableSpans survive passing via the Intent.
ParcelableSpans also allow copying text together with spans from one process to another. The process of copying and pasting text goes through the ClipboardService which, under the hood, uses the same TextUtil.writeToParcel method. So, even if you’re copying spans from your app and pasting them in the same app, this is an inter-process action and requires parceling because the text goes through ClipboardService .
By default, any class that implements Parcelable can be written and restored from a Parcel . When passing an Parcelable object between processes, the only classes that are guaranteed to be restored correctly are framework classes. If the process that tries to restore the data from a Parcel can’t construct the object because the data type is defined in a different app, then the process will crash.
There are two big caveats here:
- When text with span is passed, either in the same process or between processes, only framework’s ParcelableSpans references are kept. As a consequence, custom spans styling is not propagated.
- You can’t create your own ParcelableSpan . To avoid crashes due to unknown data types, the framework doesn’t allow implementing custom ParcelableSpan , by defining two methods, getSpanTypeIdInternal and writeToParcelInternal , as hidden. Both of them are used by TextUtils.writeToParcel .
Let’s say that you want to define a bullet point span that allows custom size for the bullet since the existing BulletSpan defines a fixed radius size of 4px. Here’s how you can implement it and what the consequences of each way are:
- Create a CustomBulletSpan that extends BulletSpan but also allows setting a parameter for the bullet size. When the span is passed either from one Activity to the other or by copying the text, the span attached to the text will be BulletSpan . This means that when the text is drawn, it will have the framework’s default bullet radius, not the one set in CustomBulletSpan .
- Create a CustomBulletSpan that just extends from LeadingMarginSpan and re-implements the bullet point functionality. When the span is passed either from one Activity to the other or by copying the text, the span attached to the text will be LeadingMarginSpan . This means that when the text is drawn, it will lose all styling.
If the desired styling can be achieved just with the framework spans, prefer applying multiple framework spans to implementing your own. Otherwise, prefer implementing custom spans that extend some of the base interfaces or abstract classes. Like this, you can avoid the framework’s implementation being applied to the spannable, when the object is passed intra- or inter-process.
By understanding how Android renders text with spans, hopefully you can use it effectively and efficiently in your app. Next time you need to style text, decide whether you should apply multiple framework spans or create your own custom span, depending on the further work you’re doing with that text.
Источник