04 7월 2018
Posted by Florina Muntenescu, Developer Advocate & Siyamed Sinir Android Text Technical Lead
In "What's new in Android P Beta" we mentioned two of the new text features in Android.. Now that Android P Beta 2 and the final APIs are here, it's time to dive deeper into what's new for text. We know that TextView is one of the most critical components of the Android view system. This is why we continue to invest in both developer- and user-facing features and API improvements.
Displaying text can be complex, encompassing features like multiple fonts, line spacing, letter spacing, text direction, line breaking, hyphenation and more. TextView has to do a lot of work to measure and lay out the given text: reading the font file, finding a glyph, decide the shape, measure the bounding box, and caching the word in an internal word cache. What's more, all of this work takes place on the UI thread, where it could potentially cause your app to drop frames.
We found that measuring text can take up to 90% of the time required to set the text. To solve this problem, in Android P and as part of Jetpack, we introduced a new API: PrecomputedText. This API is available as far back as API 14 via PrecomputedTextCompat.
PrecomputedText enables an app to perform the most time-consuming parts of text layout beforehand, even on a background thread, caching the layout result and returning valuable measurement data. The result of PrecomputedText.create(CharSequence, params) can then be set on a TextView. With this, only about 10% of the work remains to be done by the TextView.
Percentage of time taken to measure and layout text
// 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 textViewRef = ref.get() textViewRef?.text = text } }
Even with features like Smart Text Selection, precisely selecting text can be challenging. Android P introduces the text Magnifier to improve the user experience of selecting text. The magnifier helps users precisely position the cursor or the text selection handles by viewing magnified text through a pane that can be dragged over the text.
Magnifying text in Android P
We wanted users to have the same experience across all apps, whether in custom widgets or during custom text-rendering, so we provided a Magnifier
widget that can be applied to any view that is attached to a window. The Magnifier widget can provide a zoomed-in version of any view or surface, not just text.
The Magnifier has 3 main methods: show, update and dismiss. For example, you could call these methods when implementing onTouchEvent-handling for your custom view. This would cause the Magnifier to follow the user's finger along the screen.
fun onTouchEvent(event: MotionEvent): Boolean { when (event.actionMasked) { MotionEvent.ACTION_DOWN -> magnifier.show(event.x, event.y) MotionEvent.ACTION_MOVE -> magnifier.show(event.x, event.y) MotionEvent.ACTION_UP -> magnifier.dismiss() } }
The Linkify class, which has existed since API 1, allows adding links to text using regexes. On top of that, finding physical addresses spins up a WebView instance to produce the results, which can degrade the performance of the app requesting links. To make link resolution more accurate, especially for internationalized text, and to mitigate the performance degradation caused by the WebView, we created Smart Linkify. Smart Linkify can be accessed using TextClassifier API.
Smart Linkify uses machine-learning algorithms and models to recognize entities in text. This improves the reliability of the entities recognized. Smart Linkify can, based on entity type,suggest actions that the user can perform. For example, if Smart Linkify recognizes a phone number, the API suggests actions such as sending a text message, making a call, or adding to contacts.
Smart Linkify in Android P
To improve the performance of your app, move the work of generating and applying links to a background thread.
// 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 textViewRef = ref.get() textViewRef?.text = text } }
Designers sometimes provide layout specifications to developers that do not match existing TextView attributes perfectly. On Android P and in Jetpack we added three attributes, together with their corresponding functions, to help bridge this gap between how designers and developers work.
Before Android P, the spacing between lines could be controlled using the lineSpacingExtra
and lineSpacingMultiplier
attributes. However, designers will commonly provide these values as a simple line height, instead. For this reason, on Android P, we added the lineHeight
attribute to set the line height of the text: that is, the distance between the top and bottom of a line (or distance between subsequent baselines). Under the hood, this attribute actually uses and modifies the existing lineSpacingExtra
and lineSpacingMultiplier
attributes.
Size of line height and font size
<TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:text="Lorem ipsum dolor sit amet" app:lineHeight="50sp"/> // or in code TextView.setLineHeight(@Px int)
To control the distances of the first and last baselines from the view boundaries, we added two new attributes: firstBaselineToTopHeight
and lastBaselineToBottomHeight
.
firstBaselineToTopHeight
: Sets the distance between the TextView's top boundary and the baseline of the first line of the TextView. Under the hood this attribute updates the top padding.
lastBaselineToBottomHeight
: Sets the distance between the TextView's bottom boundary and the baseline of the last line of the TextView. Under the hood, this attribute actually updates the bottom padding.
Distances between first base line to top and last baseline to bottom
<TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:text="Lorem ipsum dolor sit amet" app:firstBaselineToTopHeight="28sp" app:lastBaselineToBottomHeight="20sp"/> // or in code TextView.setFirstBaselineToTopHeight(@Px int) TextView.setLastBaselineToBottomHeight(@Px int)
Text plays an important role in a vast majority of apps - it's a crucial part of an app's design language. Text is consumed by users, and it even renders emoji 😎. We're continuing to invest in text, improving the experience of both app users and developers.
To learn more about working with text APIs and what's new in Android P for text, check out the Google I/O 2018 talk on "Best practices with text":