In the previous post we created FontAwareTextAppearanceSpan. A TextAppearanceSpan descendant that can be used exactly as its parent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
but with the addition that it uses the font found in the provided style.
The thing is that this implementation works only in debug apks or if the project has AGP v4.1 and lower!
Optimizing resources
Android Gradle Plugin 4.2 introduced a number of resources optimizations in order to cut down the apk’s size. One of these optimization is the obfuscation/shortening of their filenames. This means that when the span reads the family name from the style
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
instead of getting something like res/font/acme_family.xml it gets res/Zx.xml which breaks completely FontAwareTextAppearanceSpan‘s getFont since it takes for granted that the resource’s name can be extracted from the aforementioned value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
One way to fix this is by adding android.enableResourceOptimizations=false in gradle.properties. This will prevent the optimization from happening thus allowing the extraction of the resource’s name.
But, and this is a big but, this is just a temporary fix since google has announced that from AGP v8 and on the optimizations will be hard forced with no way to change that. You can see it as a message when building while using the flag:
The option setting 'android.enableResourceOptimizations=false' is deprecated.
The current default is 'true'.
It will be removed in version 8.0 of the Android Gradle plugin.
Permanent fix
Turns out that the best way to go is to provide the font’s name ourselfs. In other words there must be a duplication of information since the name already exists in the style.
We could change FontAwareTextAppearanceSpan and pass the name in its constructor but this means that the duplication takes place in many places: in the style and in every instantiation. Also the developer instead of just using the span by providing a style, she needs to open the style, figure out the font’s name and then pass it to the constructor. Manual work that is error prone.
A better approach is to have the duplicated information at the place that gets provided instead the one that gets consumed. This leaves us with the style itself:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This way the information gets duplicated once and the developer uses the span as before by just providing the style.
Ofcourse we need to change FontAwareTextAppearanceSpan so that it reads the resource’s name from the style:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Lets say we want to use a font that is not part of the ones provided by the system.
First we create a family:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
then we add the family in a text appearance style:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
and finally we use the style either in our layout’s XML or through a TextAppearanceSpan:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Everything renders correctly as long as your min SDK is 26 since this is when the fonts in xml was introduced to the framework:
min SDK<26
When having a min SDK lower than 26 then the first thing that we need to change is our font family file. In particular we must use the app namespace instead of the android one:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Unfortunately this stops our TextAppearanceSpan from working properly:
it renders the text with all the expected properties except the needed fonts!
Why is that?
TextAppearanceSpan, upon its construction, tries to create a typeface based on the provided font family:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
getFont is added in SDK 26 and if you follow it down to resources you’ll see that it ends up in FontResourcesParser where there is a readFont:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
that tries to get the font’s name by using R.styleable.FontFamilyFont and this is where the namespace makes the difference.
Family name
So what happens when the span cannot create a typeface? In the constructor you’ll see that it loads the provided font family but only its name:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
and that name is being used, when needed, to create a typeface:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The thing is that Typeface.create loads a font only if it is a systemic one:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
so anything custom does not get found and a default is being used instead.
FontAwareTextAppearanceSpan
So, at this point we know which font we want to load and we need to find a way to use the supported way of getting it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This is one way to go. Since we know that all families live in res/font and have a .xml suffix we can clean up the name and use Resource‘s getIdentifier to figure out the font’s resource id. Now we can get the font by calling compat’s function.
What do we gain with this? We can simply replace TextAppearanceSpan with FontAwareTextAppearanceSpan and have our entire project use the new font with the minimum number of changes:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters