Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fonts, Emoji and Text Using Support Libraries

Anita
July 02, 2018

Fonts, Emoji and Text Using Support Libraries

This talk was given at Droidcon Berlin 2018 at the Cupcake stage (hence the cupcake reference :-)).

Abstract:

Android Oreo introduced exciting text changes that make it easier to use custom fonts, communicate with emojis, reduce APK size, and expand or contract textviews dynamically. Luckily, the Support Libraries from v26 onwards allow us to implement these features with backwards compatibility.

In this talk, you will learn how to implement these text changes with the help of the Support Libraries, and their limitations. You will also learn about how they are natively implemented by the framework versus the Support Libraries.

At the end of this session, you will have an increased understanding of the inner workings of Fonts in XML, Downloadable Fonts, EmojiCompat and autosizing of TextViews, as well as be ready to update your apps with them without the fear of the unknown!

Links:
Fonts in XML
https://segunfamisa.com/posts/custom-fonts-with-android-support-library
https://medium.com/google-design/the-android-developers-guide-to-better-typography-97e11bb0e261
https://www.youtube.com/watch?v=TfB-TsLFJdM

Downloadable Fonts
https://github.com/googlesamples/android-DownloadableFonts
https://developers.google.com/fonts/docs/android
https://proandroiddev.com/android-downloadable-fonts-8e60d3e146b7
https://hackernoon.com/downloadable-fonts-for-android-2041235f91e6

EmojiCompat
https://github.com/googlesamples/android-EmojiCompat
https://medium.com/exploring-android/exploring-the-android-emoji-compatibility-library-1b9f3bb724aa
http://dannyroa.com/2017/08/29/future-proof-emojis-on-android/
https://www.youtube.com/watch?v=sYGKUtM2ga8

Textview Autosizing:
https://www.youtube.com/watch?v=yvBt3YI3LZY
https://www.youtube.com/watch?v=fjUdJ2aVqE4

Anita

July 02, 2018
Tweet

More Decks by Anita

Other Decks in Technology

Transcript

  1. • Fonts in XML • Downloadable Fonts • EmojiCompat •

    Textview Auto-sizing Support Library v26+
  2. dependencies { implementation ‘com.android.support:appcompat-v7:$version’ or 
 } repositories { maven

    { url ‘https://maven.google.com' } mavenCentral() } build.gradle implementation ‘androidx.appcompat:appcompat:$version'
  3. You can now add custom font files to res/font, and

    use them as @font/lobster. What?
  4. <font-family> <font android:fontStyle="normal" android:fontWeight=“200" android:font=“@font/lato_regular“ app:fontStyle="normal" app:fontWeight=“200" app:font=“@font/lato_regular” /> <font

    android:fontStyle=“bold" android:fontWeight=“600" android:font=“@font/lato_bold” app:fontStyle=“bold" app:fontWeight=“600" app:font=“@font/lato_bold”/> </font-family> lato.xml A font family is a set of font files along with style and weight details
  5. Why? Otherwise we use custom textviews or third-party libs like

    Calligraphy or FontBinding just to use custom fonts!
  6. Why? class CustomFontTextView : TextView { constructor(context: Context) : super(context)

    { val customFont : Typeface = FontCache.getTypeface("Lato-Regular.ttf", context) this.typeface = customFont } }
 Otherwise we use custom textviews or third-party libs like Calligraphy or FontBinding just to use custom fonts!
  7. Why? class CustomFontTextView : TextView { constructor(context: Context) : super(context)

    { val customFont : Typeface = FontCache.getTypeface("Lato-Regular.ttf", context) this.typeface = customFont } }
 Otherwise we use custom textviews or third-party libs like Calligraphy or FontBinding just to use custom fonts!
  8. <com.winnie.ui.view.CustomFontTextView …… /> Why? class CustomFontTextView : TextView { constructor(context:

    Context) : super(context) { val customFont : Typeface = FontCache.getTypeface("Lato-Regular.ttf", context) this.typeface = customFont } }
 Otherwise we use custom textviews or third-party libs like Calligraphy or FontBinding just to use custom fonts!
  9. How? 
 at TypefaceCompatApi26Impl.addFontFromAssetManager(TypefaceCompatApi26Impl.java:150) at TypefaceCompatApi26Impl.createFromFontFamilyFilesResourceEntry(TypefaceCompatApi26Impl.java:218) at TypefaceCompat.createFromResourcesFamilyXml(TypefaceCompat.java:116) at ResourcesCompat.loadFont(ResourcesCompat.java:249)

    at ResourcesCompat.loadFont(ResourcesCompat.java:213) at ResourcesCompat.getFont(ResourcesCompat.java:206) at TintTypedArray.getFont(TintTypedArray.java:119) at AppCompatTextHelper.updateTypefaceAndStyle(AppCompatTextHelper.java:208) at AppCompatTextHelper.loadFromAttributes(AppCompatTextHelper.java:152) at AppCompatTextHelperV17.loadFromAttributes(AppCompatTextHelperV17.java:38) at AppCompatButton.<init>(AppCompatButton.java:77) at android.support.v7.widget.AppCompatButton.<init>(AppCompatButton.java:67) at createView(AppCompatViewInflater.java:109) at AppCompatDelegateImplV9.createView(AppCompatDelegateImplV9.java:1024) at AppCompatDelegateImplV9.onCreateView(AppCompatDelegateImplV9.java:1081) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:772) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730) at android.view.LayoutInflater.rInflate(LayoutInflater.java:863) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824) at android.view.LayoutInflater.rInflate(LayoutInflater.java:866) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824) at android.view.LayoutInflater.rInflate(LayoutInflater.java:866) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824) at android.view.LayoutInflater.inflate(LayoutInflater.java:515) at android.view.LayoutInflater.inflate(LayoutInflater.java:423) at android.view.LayoutInflater.inflate(LayoutInflater.java:374) at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:287) at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139) AAPT and AppCompat magic!
  10. <TextView …… android:fontFamily=“@font/lato”/>//can also reference custom font file here <style

    name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Applying Lato
  11. <TextView …… android:fontFamily=“@font/lato”/>//can also reference custom font file here <style

    name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Applying Lato
  12. <TextView …… android:fontFamily=“@font/lato”/>//can also reference custom font file here <style

    name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> val typeface = ResourcesCompat.getFont(context, R.font.lato) textView.typeface = typeface Applying Lato
  13. • API 14+ • Make sure to use both android

    and app name spaces • Need to use AppCompat and subclass AppCompatActivity
 Gotchas
  14. • Results in reduced APK size — which can significantly impact your

    app installs! • Sharing fonts results in less usage of precious memory, disk space and cellular data 
 
 Why?
  15. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  16. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  17. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } val handlerThread = HandlerThread("fonts") handlerThread.start() val handler = Handler(handlerThread.looper) val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  18. Programmatically using Support Library val callback = object : FontsContractCompat.FontRequestCallback(){

    override fun onTypefaceRetrieved(typeface: Typeface) { downloadableFontTextView.typeface = typeface } override fun onTypefaceRequestFailed(reason: Int) { } } //make sure to call this on background thread
 FontsContractCompat .requestFont(this@MainActivity, fontRequest, callback, handler) val handlerThread = HandlerThread("fonts") handlerThread.start() val handler = Handler(handlerThread.looper) val fontRequest = FontRequest("com.google.android.gms.fonts", "com.google.android.gms", “Lato", R.array.font_certs) //can generate certs from Android Studio
  19. Using Android Studio & Fonts in XML (recommended) Select a

    text view and click on fontFamily under Attributes in the graphical layout.
  20. font_certs.xml The system uses these certificates to verify the provider’s

    identity, to avoid getting fonts from an unknown source. <resources> <array name="com_google_android_gms_fonts_certs"> <item>@array/com_google_android_gms_fonts_certs_dev</item> <item>@array/com_google_android_gms_fonts_certs_prod</item> </array> <string-array name="com_google_android_gms_fonts_certs_dev"> <item> <!-- string cert --> </item> </string-array> <string-array name="com_google_android_gms_fonts_certs_prod"> <item> <!-- string cert —> </item> </string-array> </resources>
  21. preloaded-fonts.xml When referenced in the manifest, the framework can pre-load

    fonts to avoid delays when the app is launched. <resources> <array name=“preloaded_fonts" translatable="false"> <item>@font/lato</item> <item>@font/lato_bold</item> </array> </resources>
  22. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/>
  23. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/>
  24. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/> Now you can apply the downloaded font using Fonts in XML!
  25. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Now you can apply the downloaded font using Fonts in XML!
  26. Make sure this line is added to your app’s Manifest

    file, Android Studio should have done this automatically. <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts"/> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name=“android:fontFamily">@font/lato</item> …… </style> Now you can apply the downloaded font using Fonts in XML!
  27. • API 14+, AS 3.0+ if using it to generate

    font files • Using Google Play Services as a font provider only works with version 11+, else it uses the default system font Gotchas
  28. • API 14+, AS 3.0+ if using it to generate

    font files • Using Google Play Services as a font provider only works with version 11+, else it uses the default system font • FontsContractCompat in v27.0.2 has a non-trivial crash that has been fixed in v27.1.0 Gotchas
  29. • ResourcesCompat.getFont() can throw ResourcesNotFoundException if font has not been

    downloaded yet • You will likely have to write your own FontProvider when using a non-Google custom font Gotchas
  30. • ResourcesCompat.getFont() can throw ResourcesNotFoundException if font has not been

    downloaded yet • You will likely have to write your own FontProvider when using a non-Google custom font • Fonts are very persistent once downloaded! For testing : data/data/com.google.android.gms/files/fonts Gotchas
  31. • EmojiCompat helps prevent our apps from showing missing emoji

    characters (“tofu” □) • Helps implement backward-compatible emoji support on devices running Android 4.4+ What?
  32. By using EmojiCompat, your app users do not need to

    update their OS to get the latest emoji! Why?
  33. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs)
  34. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val config = FontRequestEmojiCompatConfig(applicationContext, fontRequest) .registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() { } override fun onFailed(throwable: Throwable?) { } })
 val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs)
  35. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val config = FontRequestEmojiCompatConfig(applicationContext, fontRequest) .registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() { } override fun onFailed(throwable: Throwable?) { } })
 val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs)
  36. Downloadable Fonts Config dependencies { //com.android.support:support-emoji implementation “androidx.emoji:emoji:$version” implementation “androidx.emoji:emoji-appcompat:$version”

    } val config = FontRequestEmojiCompatConfig(applicationContext, fontRequest) .registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() { } override fun onFailed(throwable: Throwable?) { } })
 val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs) EmojiCompat.init(config)
  37. Preloading for EmojiCompat <meta-data android:name="fontProviderRequests" android:value="Noto Color Emoji Compat"/> Google

    Play Services can download the font during install or update if added to the app’s Manifest file.
  38. Preloading for EmojiCompat <meta-data android:name="fontProviderRequests" android:value="Noto Color Emoji Compat"/> Google

    Play Services can download the font during install or update if added to the app’s Manifest file.
  39. Bundled Fonts Config 
 
 EmojiCompat.Config config = new BundledEmojiCompatConfig(this)


    //optional properties .setReplaceAll(true) .setEmojiSpanIndicatorEnabled(true) .setEmojiSpanIndicatorColor(Color.WHITE) EmojiCompat.init(config); dependencies { ... implementation "com.android.support:support-emoji-bundled:$version" }
  40. Bundled Fonts Config 
 
 EmojiCompat.Config config = new BundledEmojiCompatConfig(this)


    //optional properties .setReplaceAll(true) .setEmojiSpanIndicatorEnabled(true) .setEmojiSpanIndicatorColor(Color.WHITE) EmojiCompat.init(config); dependencies { ... implementation "com.android.support:support-emoji-bundled:$version" }
  41. Bundled Fonts Config 
 
 EmojiCompat.Config config = new BundledEmojiCompatConfig(this)


    //optional properties .setReplaceAll(true) .setEmojiSpanIndicatorEnabled(true) .setEmojiSpanIndicatorColor(Color.WHITE) EmojiCompat.init(config); Adds ~7MB to your APK, but works on devices without Google Play Services 11+ dependencies { ... implementation "com.android.support:support-emoji-bundled:$version" }
  42. Using EmojiCompat EmojiCompat.get().registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() {

    val compat = EmojiCompat.get() textInputEditText.text = compat.process( context.getString(R.string.default_text, EMOJI)) } override fun onFailed(throwable: Throwable?) { textInputEditText.text = context.getString(R.string.default_text_without_emoji) } })
  43. Using EmojiCompat EmojiCompat.get().registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized() {

    val compat = EmojiCompat.get() textInputEditText.text = compat.process( context.getString(R.string.default_text, EMOJI)) } override fun onFailed(throwable: Throwable?) { textInputEditText.text = context.getString(R.string.default_text_without_emoji) } })
  44. Using EmojiCompat EmojiCompat.get().unregisterInitCallback(…); EmojiCompat.get().registerInitCallback(object : EmojiCompat.InitCallback() { override fun onInitialized()

    { val compat = EmojiCompat.get() textInputEditText.text = compat.process( context.getString(R.string.default_text, EMOJI)) } override fun onFailed(throwable: Throwable?) { textInputEditText.text = context.getString(R.string.default_text_without_emoji) } })
  45. Using EmojiCompat with Custom Widgets class CustomTextView(..) : AppCompatTextView(..) {


    private val emojiTextViewHelper: EmojiTextViewHelper get() { if (mEmojiTextViewHelper == null) { mEmojiTextViewHelper = EmojiTextViewHelper(this) } return mEmojiTextViewHelper as EmojiTextViewHelper }
 override fun setFilters(filters: Array<InputFilter>) { super.setFilters(emojiTextViewHelper.getFilters(filters)) } override fun setAllCaps(allCaps: Boolean) { super.setAllCaps(allCaps) emojiTextViewHelper.setAllCaps(allCaps) } init { emojiTextViewHelper.updateTransformationMethod() }
  46. Using EmojiCompat with Custom Widgets class CustomTextView(..) : AppCompatTextView(..) {


    private val emojiTextViewHelper: EmojiTextViewHelper get() { if (mEmojiTextViewHelper == null) { mEmojiTextViewHelper = EmojiTextViewHelper(this) } return mEmojiTextViewHelper as EmojiTextViewHelper }
 override fun setFilters(filters: Array<InputFilter>) { super.setFilters(emojiTextViewHelper.getFilters(filters)) } override fun setAllCaps(allCaps: Boolean) { super.setAllCaps(allCaps) emojiTextViewHelper.setAllCaps(allCaps) } init { emojiTextViewHelper.updateTransformationMethod() }
  47. Using EmojiCompat with Custom Widgets class CustomTextView(..) : AppCompatTextView(..) {


    private val emojiTextViewHelper: EmojiTextViewHelper get() { if (mEmojiTextViewHelper == null) { mEmojiTextViewHelper = EmojiTextViewHelper(this) } return mEmojiTextViewHelper as EmojiTextViewHelper }
 override fun setFilters(filters: Array<InputFilter>) { super.setFilters(emojiTextViewHelper.getFilters(filters)) } override fun setAllCaps(allCaps: Boolean) { super.setAllCaps(allCaps) emojiTextViewHelper.setAllCaps(allCaps) } init { emojiTextViewHelper.updateTransformationMethod() }
  48. • hasEmojiGlyph(charSequence, metadataVersion) can check if app can render a

    specific emoji • EDITOR_INFO_METAVERSION_KEY from EditorInfo.extras() bundle EmojiCompat for IMEs
  49. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis Gotchas
  50. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis • process() only works on widgets that can render spanned instances Gotchas
  51. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis • process() only works on widgets that can render spanned instances • Make sure to handle the failure cases in the callbacks, otherwise the text may not get set at all Gotchas
  52. • API 19+. Can still use it if supporting lower

    versions, it just won’t render latest emojis • process() only works on widgets that can render spanned instances • Make sure to handle the failure cases in the callbacks, otherwise the text may not get set at all • When using Downloadable Fonts, all its gotchas apply Gotchas
  53. Material design recommends using dynamic types instead of using smaller

    type sizes or truncating larger size text. Why?
  54. Using XML <TextView android:layout_width="match_parent" android:layout_height="200dp" app:autoSizeTextType=“uniform” //or none
 //if want

    to specify granularity app:autoSizeMinTextSize="12sp" app:autoSizeMaxTextSize="100sp" app:autoSizeStepGranularity="2sp" />
  55. Using XML <TextView android:layout_width="match_parent" android:layout_height="200dp" app:autoSizeTextType=“uniform” //or none
 //if want

    to specify granularity app:autoSizeMinTextSize="12sp" app:autoSizeMaxTextSize="100sp" app:autoSizeStepGranularity="2sp" /
  56. Using XML <TextView android:layout_width="match_parent" android:layout_height="200dp" app:autoSizeTextType=“uniform” //or none
 //if want

    to specify granularity app:autoSizeMinTextSize="12sp" app:autoSizeMaxTextSize="100sp" app:autoSizeStepGranularity="2sp" / <TextView android:layout_width="match_parent" android:layout_height="200dp" app:autoSizeTextType="uniform" app:autoSizePresetSizes="@array/autosize_text_sizes" />
  57. Using XML <TextView android:layout_width="match_parent" android:layout_height="200dp" app:autoSizeTextType=“uniform” //or none
 //if want

    to specify granularity app:autoSizeMinTextSize="12sp" app:autoSizeMaxTextSize="100sp" app:autoSizeStepGranularity="2sp" / <TextView android:layout_width="match_parent" android:layout_height="200dp" app:autoSizeTextType="uniform" app:autoSizePresetSizes="@array/autosize_text_sizes" /> <resources> <array name="autosize_text_sizes"> <item>10sp</item> <item>20sp</item> <item>40sp</item> </array> </resources>
  58. Programmatically //can pass in TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM or NONE 
 TextViewCompat.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType)

    TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(autoSizeMinT extSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit)
  59. Programmatically //can pass in TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM or NONE 
 TextViewCompat.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType)

    TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(autoSizeMinT extSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit) TextViewCompat.setAutoSizeTextTypeUniformWithPresetSizes(textView, presetSizesArray, unit)
  60. Programmatically //can pass in TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM or NONE 
 TextViewCompat.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType)

    TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(autoSizeMinT extSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit) TextViewCompat.setAutoSizeTextTypeUniformWithPresetSizes(textView, presetSizesArray, unit) The default dimensions for uniform scaling are minTextSize = 12sp, maxTextSize = 112sp, and granularity = 1px.

  61. • API 14+ • Don’t use "wrap_content" when autosizing textviews,

    leads to unexpected results • Words might split instead of autosizing to fit, there could be variable gap between views, so make sure to test, test, test! Gotchas
  62. • API 14+ • Don’t use "wrap_content" when autosizing textviews,

    leads to unexpected results • Words might split instead of autosizing to fit, there could be variable gap between views, so make sure to test, test, test! • If not using AppCompat components like AppCompatActivity, then use AppCompatTextView Gotchas