Fonts

Fonts (revisited)

In a previous series we took an early look at the new font support coming in Android O. At Google IO 2017 the new font support was formally announced, and in this article we’ll take a look at what has changed since the original series.


Source
Previously we took a look at how we can directly use custom fonts directly in our layouts, but there were two main issues: Firstly, the mechanism for specifying different font styles (such as bold and italic) did not work; secondly this was only supported in Android O with no backward compatibility. There are some updates for both of these issues and, in this article, we’ll take a deeper look.

The first of these issues is the fact that font styles were not fully implemented in the initial O developer preview. As I said in the previous article there was either a bug or an incomplete implementation at that stage. But this has now been fixed and we can now specify different variants using the android:textStyle attribute:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.stylingandroid.o.fonts.MainActivity">

  <TextView
    android:id="@+id/pacifico"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:fontFamily="@font/pacifico"
    android:text="@string/pangram"
    android:textSize="20sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

  <TextView
    android:id="@+id/nunito_regular"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:fontFamily="@font/nunito"
    android:text="@string/pangram"
    android:textSize="20sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/pacifico" />

  <TextView
    android:id="@+id/nunito_bold"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:fontFamily="@font/nunito"
    android:text="@string/pangram"
    android:textSize="20sp"
    android:textStyle="bold"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/nunito_regular" />

  <TextView
    android:id="@+id/nunito_italic"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:fontFamily="@font/nunito"
    android:text="@string/pangram"
    android:textSize="20sp"
    android:textStyle="italic"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/nunito_bold" />

  <TextView
    android:id="@+id/nunito_bold_italic"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:fontFamily="@font/nunito"
    android:text="@string/pangram"
    android:textSize="20sp"
    android:textStyle="bold|italic"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/nunito_italic" />

  <TextView
    android:id="@+id/nunito_programmatic"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:text="@string/pangram"
    android:textSize="20sp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/nunito_bold_italic" />

</android.support.constraint.ConstraintLayout>

If we run that we can see that the different variants now render correctly:

So what about the backwards compatibility issue? That’s a mixture of good and bad news. The good news is that version 26.0.0-beta1 of support-v4 library now includes support for including custom fonts in your APK and defining them as XML. First we need to add this dependency and reduce the minSdkVersion:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 'android-O'
    buildToolsVersion "26.0.0 rc2"
    defaultConfig {
        applicationId "com.stylingandroid.o.fonts"
        minSdkVersion 21
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:support-v4:26.0.0-beta1'
}

I also dropped targetSdkVersion because it won’t install on pre-O devices if we target the O preview.

The only caveat to declaring our fonts in XML is that we must declare your typefaces using both the android and app namespaces to ensure full compatibility:

<font-family xmlns:tools="http://schemas.android.com/tools"
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  tools:ignore="UnusedAttribute">

  <font
    android:font="@font/nunito_regular"
    android:fontStyle="normal"
    android:fontWeight="400"
    app:font="@font/nunito_regular"
    app:fontStyle="normal"
    app:fontWeight="400" />

  <font
    android:font="@font/nunito_italic"
    android:fontStyle="italic"
    android:fontWeight="400"
    app:font="@font/nunito_italic"
    app:fontStyle="italic"
    app:fontWeight="400" />

  <font
    android:font="@font/nunito_bold"
    android:fontStyle="normal"
    android:fontWeight="700"
    app:font="@font/nunito_bold"
    app:fontStyle="normal"
    app:fontWeight="700" />

  <font
    android:font="@font/nunito_bold_italic"
    android:fontStyle="italic"
    android:fontWeight="700"
    app:font="@font/nunito_bold_italic"
    app:fontStyle="italic"
    app:fontWeight="700" />

</font-family>

(Note: how the UnusedAttribute warning is suppressed otherwise lint warns that the attributes in the android namespace are unused.

Finally we need to change how we apply our typeface programmatically to use ResourcesCompat to load the Typeface:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Typeface nunito = ResourcesCompat.getFont(this, R.font.nunito);
        TextView text = (TextView) findViewById(R.id.nunito_programmatic);
        text.setTypeface(nunito, Typeface.BOLD_ITALIC);
    }
}

If we run this on a device running the O developer preview, it appears as before. However, if we run this on a device running Marshmallow (API 23) we get the following:

Although the correct styles have been applied, it is only the final TextView which has had the correct font applied and this is the one to which we applied the Typeface programmatically. In the official documentation there is mention of applying things programmatically, but nothing with regard to whether specifying a font using the fontFamily attribute in XML is supported.

There is actually a real gotcha here. In order to keep the example project as lean as possible, the original implementation targeted Android O only and, as a result of this approach, my MainActivity subclasses Activity. However, it seems that for the support library fonts support we actually need to use AppCompat and subclass AppCompatActivity. For those that understand how AppCompat works this will make perfect sense, but for those that don’t: AppCompat uses a decorator pattern around existing view types and has its own view inflation mechanism to substitute these in during layout inflation. It is using this mechanism that the font support has been added, so things simply don’t work unless we are using AppCompat.

So, we must convert our project. First our Activity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Typeface nunito = ResourcesCompat.getFont(this, R.font.nunito);

        TextView text = findViewById(R.id.nunito_programmatic);
        text.setTypeface(nunito, Typeface.BOLD_ITALIC);
    }
}

Next our theme:

<resources>

  <!-- Base application theme. -->
  <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
  </style>

</resources>

As well as changing the parent theme, we must also remove the `android` namespace from our item names.

Finally our build script:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 'android-O'
    buildToolsVersion "26.0.0 rc2"
    defaultConfig {
        applicationId "com.stylingandroid.o.fonts"
        minSdkVersion 21
        targetSdkVersion 'O'
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
}

apply from: '../config/static_analysis.gradle'

If we do this, we now have the correct fonts being rendered. However, there is something of of a discrepancy in how the weights are rendered on O compared to earlier versions:

The bold fonts in Marshmallow (on the left) are not quite as strong as those for Android O (on the right) this has been raised as an issue. Moreover, the weights of the bottom two TextViews in Marshmallow should be identical, but they are not. This would suggest a bug in how weights are being applied via XML. Hopefully this is also something which will get resolved before we have a final version of the support library.

So the fonts support has improved quite considerably and are certainly useable (provided you are using AppCompat), but there is still one minor wrinkle.

The source code for this article is available here.

© 2017, Mark Allison. All rights reserved.

Copyright © 2017 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

5 Comments

  1. Hello Mark,
    Great article!
    I am facing a problem though, I am trying to use Android O fonts feature with AS 2.3.3 (which supports Android O features, as mentioned in its release notes). But element is not getting resolved, can you help?
    Thanks!

    1. Sorry, I don’t understand your problem, and really don’t have any available bandwidth to provide free developer support. Perhaps you should try here.

  2. Hi Mark,
    I have an issue with the font. If I use an Activity that extends AppCompatActivity(i.e class TestActivity : AppcompatActivity()), the fonts works fine – https://ibb.co/cT4J9v

    If I use an Activity that extends AppCompLifecycleActivityatActivity(i.e class TestActivity : LifecycleActivity()), the fonts does not work – https://ibb.co/kJzkpv

    Thanks in advance

    1. Without trying it myself I don’t have a definitive answer, but it sounds like you should extend AppCompatActivity and then you can manually implement LifecycleOwner as described here

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.