Custom and Downloadable Fonts on Android
See how to make great looking apps using the new custom and downloadable fonts capability available in Android Studio 3.0, all in Kotlin. By Ivan Kušt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Custom and Downloadable Fonts on Android
25 mins
- History: Difficulties in the Past
- Getting Started
- Requirements
- Add the latest support library
- Bundled fonts
- Creating a font family
- Custom fonts in layouts
- Custom fonts programatically
- Downloadable fonts
- How Font Providers work
- Security & certificates
- Downloadable fonts programatically
- Creating a font request
- Retrieving font information
- Downloadable fonts as XML resources
- Pre-declaring fonts in manifest
- Where To Go From Here?
Custom fonts in layouts
You’ve already seen in previous steps how to add a custom font to TextView
. Now you will add a custom font to a Theme, changing the default font on all Activities that use the Theme.
Open the file res/values/styles.xml.
Change app theme Theme.FontQuiz
– add the fontFamily
attribute:
<style name="Theme.FontQuiz" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="fontFamily">@font/opensans</item>
</style>
Build and run the app.
You can see that across the app, OpenSans is now used:
Custom fonts programatically
You can set the custom font programatically as well. To do that you will use the ResourcesCompat
class from the support library. Type the following at the end of the onCreate()
method in MainActivity
:
val typeface = ResourcesCompat.getFont(this, R.font.opensans_bold)
startButton.typeface = typeface
Build and run your project.
You can see that the font on the start button has been set to OpenSans Bold.
Note again that you use the support library to support Android versions earlier than Android 8.0.
Downloadable fonts
Now that you’ve seen how custom fonts work, let’s jump onto another novelty – downloadable fonts. Downloadable fonts allow you to add fonts to your application that download on demand or when your application starts.
This has more benefits:
- fonts get downloaded only when required
- reduced application .apk size
- more applications can share fonts through font providers which can reduce used disk space
How Font Providers work
Font providers take care of retrieving and caching downloadable fonts used across applications. This is what the process of requesting a font looks like:
All applications that use downloadable fonts pass their requests via FontsContractCompat
. It then communicates with the requested font provider. A font provider is an application that takes care of fetching and caching the appropriate fonts. There can be more of them installed on a device but currently only a Google font provider is available.
Security & certificates
To ensure security when using font providers, you have to provide the certificate used to sign by the font provider. This enables Android to verify the identity of the font provider. You have to do this for font providers that are not pre-installed on the device or when using support library.
Your next task is to add the certificate for the Google font provider.
Click on the res\values folder, press ⌘N (or File\New) and select Values resource file.
In the dialog, name it font_certs and click Ok.
You define font provider certificates in a string-array
. If the font provider has more than one set of certificates, then you must define an array of string arrays. The Google font provider used with the support library uses two sets of certificates, and the next step is to define an array for each set.
Add a string array in the new file by adding <string-array>
in the <resources>
section and name it com_google_android_gms_fonts_certs_dev.
Add a single item to it with the following content:
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
Now add another string array with the name com_google_android_gms_fonts_certs_prod and add a single item to it with the following content:
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
Finally, create an array named com_google_android_gms_fonts_certs and add the two previously defined string arrays as its items.
Your font_certs.xml file should now look like this:
<?xml version="1.0" encoding="utf-8"?>
<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>
MIIEqDCCA5CgAwIBA…
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0…
</item>
</string-array>
</resources>
Build and run your project.
There is no visible change but you are now ready to add downloadable fonts.
Downloadable fonts programatically
The FontQuiz application is still missing one key feature – text in quiz questions always must be in font from the question.
You can implement requests to fetch downloadable fonts and apply them to a View
in code as well. You must use the FontsContractCompat
class from the support library to support Android versions older than 8.0.
Your task will be to use it to request and set a random font on the quiz question Activity
.
Open QuestionActivity
and find the showFont()
method.
Font family names available for the quiz are in a list in res\values\family_names.xml file. The logic to pick a random font for the question and four offered answers is already there. Your job is to request and show the font with the name passed to showFont()
.
First, hide all the buttons and show a ProgressView
that will show hat font is loading by adding:
buttons.forEach { button -> button.isEnabled = false }
progressView.visibility = View.VISIBLE
Build and run your project.
Click on “Start Quiz” and you’ll see disabled buttons and a progress indicator when the first question opens up:
Time to add the font request.
Creating a font request
Your next task is to add requesting downloadable fonts to QuestionActivity.
Create a query string and a request for the downloadable font:
val query = "name=$familyName"
val request = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
query,
R.array.com_google_android_gms_fonts_certs
)
FontRequest
class from android.support.v4.provider
package. The one from android.provider
is not compatible with support library.
When creating a FontRequest
you have to pass:
-
provider authority – only Google provider
com.google.android.gms.fonts
is available so far -
provider package – for Google font provider it’s
com.google.android.gms
- query – query string that describes the font you are requesting
- array of certificates – to verify the provider
To request a new font use requestFont()
method from FontsContractCompat
. Add following to the end of showFont()
:
FontsContractCompat.requestFont(
this,
request,
object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface?) {
}
override fun onTypefaceRequestFailed(reason: Int) {
}
},
handler
)
Requesting a downloadable font is an asynchronous operation. Method requestFont()
passes the result through a FontsContractCompat.FontRequestCallback
interface. If the request is successful, FontContractorCompat
calls onTypefaceRetreived()
. Use the Typeface
instance passed to set the font on a View. Enable all the buttons and hide progress indicator:
override fun onTypefaceRetrieved(typeface: Typeface?) {
buttons.forEach { button -> button.isEnabled = true }
progressView.visibility = View.INVISIBLE
fontTextView.typeface = typeface
}
In case of an error, FontContractorCompat
will call onTypefaceRequestFailed()
. Use it to display an error message by calling showError()
and passing it error code:
override fun onTypefaceRequestFailed(reason: Int) {
showError(reason)
}
The last thing you need when requesting fonts is a Handler
instance.
Handler
enables you to send code to a different thread which it will then execute.
FontContractorCompat
uses it to execute retrieving a font on a Thread
associated with that Handler
. Make sure you provide a Handler
that is not associated with a UI thread.
val handlerThread = HandlerThread("fonts")
handlerThread.start()
handler = Handler(handlerThread.looper)
For convenience, create a private field that will hold the handler and a property that will initialize and retrieve it:
private var handler: Handler? = null
private val handlerThreadHandler: Handler
get() {
if (handler == null) {
val handlerThread = HandlerThread("fonts")
handlerThread.start()
handler = Handler(handlerThread.looper)
}
return handler ?: throw AssertionError("Set to null by another thread")
}
Using the handlerThreadHandler
property will initialize the handler on the first use and return it.
The call at the end of showFont()
should now look like:
FontsContractCompat.requestFont(
this,
request,
object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface?) {
buttons.forEach { button -> button.isEnabled = true }
progressView.visibility = View.INVISIBLE
fontTextView.typeface = typeface
}
override fun onTypefaceRequestFailed(reason: Int) {
showError(reason)
}
},
handlerThreadHandler
)
Build and run your project. Start the quiz:
Now you can see the text on each question in the appropriate font! :]