The TextView is one of the most common Views used across all the Android apps. It has very simple API to show the text on the screen. Not only a simple text, but also styled text with clickable parts. So if everything is that simple, why we need new API for text?
Before answering that question let’s dive bit dipper and understand what is actually happening when you are writing a code like this:
textView.text = "Hello World"
Text in Android can be separated to two parts: Java and Native (C++).
In the Java part we have the TextView on the top, and on the second layer are 3 components: Layout, Paint and Canvas which have everything needed to create, style and show a text. These 3 can be used in CustomViews if you want to have a text without using TextView itself.
On the top of the Native part is sutiated a library called Minikin which is responsable for layouting the words, linebreaking and hyphenation. There are also some more libraries in Native part, which are responsible for Unicode, text shaping, etc.
Defining the text
Let’s see what steps we should do to have our final text:
1. At the beginning, Minikin is trying to identify glyphs for the given string. The glyph is a visual representation of the character, but the connection between glyph and character is not always one to one. In some languages based on fonts we are using some characters can be joint to a single glyph. This is called ligature. E.g. in Latin fi is joined to ﬁ, or in Armenian մն can be joined to ﬓ. In some cases, we can have a text with different languages in it, and sometimes, the glyphs cannot be identified within one font, in this case two or more fonts can be used to identify all the glyphs.
2. Once all the glyphs are identified, it’s time to measure the size of the text. The measurement is based on text size, text style (bold, italic, …), locale and some other factors. Each time we change any of them the measure() function of TextView is called again. Beacuase this process is pretty expensive, there is a system wide LRU word cache, where the system is saving all the measured words, and next time, when it needed, the word is not measuring again, but it is taking the measurement value from the cache.
3. Now we have both the glyphs and the results of measures, and we can position the words on the screen. In the simplest case the words will be positioned side by side, while there is a place on the screen, then the next word will be moved to a new line. This case is
android:breakStrategy = "simple"
The next possible value is
android:breakStrategy = "balanced"
In this case the lines will be more balanced, to have almost similar widths. The dafualt value for this attribute is
android:breakStrategy = "high_quality"
It’s behavior is almost the same like balanced, but there are some differences. E.g. for the last line there will be a hyphenation.
Hyphenation itself is affecting the measurement process and can slow down it 2–2.5x. The problem is that for each word Minikin should calculate the possibility if hyphentation.
From Android Q and AppCompat 1.1.0 the hyphenation is turned off by default, and if you need it, you can turn it in by using this attribute
android:hyphenationFrequency = "full"
Once all these steps are passed, we can see the desired text on the screen. The problem is, that all this calculations are being done on the app’s Main Thread and that would be nice to have a tool to do all this on the Background Thread, and why not on the Renderer Thread, which we have since Android L.
The soulition for this problem is PrecomputedText. It was added from the Android P and with it we can do all text measurement on any thread we want.
PrecomputedText gives us a simple API to use it and there is no need to have a new text setter functions, because the PrecomputedText class is implementing Spannable.
Let’s see an example how can we use the PrecomputedText.
Only the creation of the PrecomputedText is being done on the Background Thread. The rest of the code is executed on the Main Thread.
We can see the power of the PrecomputedText in RecyclerView. Since SupportLibrary 25.1 RecyclerView has a Prefetch functionality, which is preloading of the future item views, we can bind the PrecomputedText to it and not worry about thread creation. For it we have two helper functions, which are setTextFuture() and getTextFuture(). The example code is bellow.
The important thing with this is that you can’t change text metrics after setTextFuture() is called. Everything should be set before.
With the help of PrecomputedText we can move 90% of expensive text work to the Background Thread.
- Do not turn on hyphenation, if you really don’t need it (or if your designer is not forcing you to use it 🥺)
- Use precomputed text, if your TextView will have more than 200 characters (otherwise the difference won’t be huge)
- Do not change text styling after setting text future
- If you are using a custom Layout Manager with your RecyclerView, make sure to implement collectAdjacentPrefetchPositions() to enable prefetch (all default Layout Managers have this feature enabled out of the box)
Some links to get more about the topic