This is the first day of my participation in Gwen Challenge

preface

Nice to meet you

I recently received a request to replace the default fonts in the current project globally and introduce some new fonts provided by the UI designer. So I did some research on fonts and shared some of my experience with you.

Note: the system source code shown in this article is based on Android-30, and extract the core parts for analysis

Introduction to Android default fonts

Android uses a font called Roboto by default, which is a font portal recommended by Google. It offers a variety of font options, such as bold, italic, and so on.

2, in Android, we usually directly or indirectly through the TextView control to bear the font display, because Android provides bearing font display controls will inherit TextView directly or indirectly, for example: EditText, Button, etc.

3, TextView has three properties to set the font display:

1), textStyle

2), the typeface

3), fontFamily

Let’s focus on these three attributes

Second, the textStyle

TextStyle is used to set the font style. Let’s take a look at it in TextView’s custom properties:

// The custom property of TextView textStyle<attr name="textStyle">
    <flag name="normal" value="0" />
    <flag name="bold" value="1" />
    <flag name="italic" value="2" />
</attr>
Copy the code

From the above custom attributes we know:

1. TextStyle has three main styles:

  • Normal: default font
  • Bold: bold
  • Italic: italic

2. TextStyle is carried by flag, and the value represented by flag can be done or calculated, that is to say, we can set a variety of font styles for superposition

Let’s set it up in XML as shown below:

As you can see, the TextView’s textStyle property is superimposed with bold and italic styles, and you can see the preview effect on the right

We can also set it in code, but only one font style can be set in code, not superimposed:

mTextView.setTypeface(null, Typeface.BOLD)
Copy the code

Three, the typeface

Typeface is used to set the font of the TextView.

//TextView's custom property Typeface<attr name="typeface">
    <enum name="normal" value="0" />
    <enum name="sans" value="1" />
    <enum name="serif" value="2" />
    <enum name="monospace" value="3" />
</attr>
Copy the code

From the above custom attributes we know:

Typeface provides 4 typefaces:

  • Noraml: common font, used by the system by default
  • Sans: non-serif font
  • Serif: Serif font
  • Monospace: equal width font

Typeface is loaded with enum, enum indicates the enumeration type, and only one font can be selected at a time. Therefore, we can only set one font at a time, not overlay

Let’s set it up in XML as shown below:

A brief introduction to the differences between these fonts:

Serif: Additional decoration at the beginning and end of the stroke, and the thickness of the stroke varies depending on the height of the stroke

Sans (not a serif) : no serif font these additional decoration, and a noraml font is the same

Monospace: Limits the width of each character so that they are equal in width

We can also set this in code:

mTv.setTypeface(Typeface.SERIF)
Copy the code

Four, fontFamily

FontFamily is an enhanced version of Typeface. It represents a set of fonts supported by The Android operating system. Each font has an alias.

//TextView's custom property fontFamily<attr name="fontFamily" format="string" />
Copy the code

From the above custom attributes we know:

FontFamily receives a String value, which means we can set the font alias, as shown in the following figure:

As you can see, it carefully distinguishes the styles of each family of fonts, and we also set it in XML:

We are setting it in the code:

mTv.setTypeface(Typeface.create("sans-serif-medium",Typeface.NORMAL))
Copy the code

The value of the note is: FontFamily has compatibility issues with certain fonts, such as the sans-serif-medium font I set above. This font will only take effect on the Android version if it is greater than or equal to 21. If it is smaller than 21, the default font will be used. So we need to pay attention to this when using the fontFamily property

At this point, we’ve covered the three properties that affect Android fonts, but I have a question in my mind 🤔️? If I set these three properties at the same time, will they all work?

With this question in mind, let’s explore the source code

V. Analysis of the relationship among textStyle, Typeface and fontFamily

TextView needs to be initialized before we can use it, and we end up calling the constructor with the most arguments:

public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  	// omit the ton code.....
  	// Read the set properties
  	readTextAppearance(context, appearance, attributes, false /* styleArray */);
  	// Set the font
  	applyTextAppearance(attributes);
 }

private void applyTextAppearance(TextAppearanceAttributes attributes) {
   	// omit the ton code.....
  	setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
                attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
}
Copy the code

The above call chain will first read the related properties of the TextView. Let’s look at some of the related properties of the font:

private void readTextAppearance(Context context, TypedArray appearance,
            TextAppearanceAttributes attributes, boolean styleArray) {
  	/ /...
  	switch (index) {
 	    case com.android.internal.R.styleable.TextAppearance_typeface:
                attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
                if(attributes.mTypefaceIndex ! = -1 && !attributes.mFontFamilyExplicit) {
                    attributes.mFontFamily = null;
                }
                break;
            case com.android.internal.R.styleable.TextAppearance_fontFamily:
                if(! context.isRestricted() && context.canLoadUnsafeResources()) {try {
                        attributes.mFontTypeface = appearance.getFont(attr);
                    } catch (UnsupportedOperationException | Resources.NotFoundException e) {
                        // Expected if it is not a font resource.}}if (attributes.mFontTypeface == null) {
                    attributes.mFontFamily = appearance.getString(attr);
                }
                attributes.mFontFamilyExplicit = true;
                break;
            case com.android.internal.R.styleable.TextAppearance_textStyle:
                attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
                break;
            / /...
   	    default:}}Copy the code

From the above code we can see:

1. When we set the Typeface property, we assign the property value to mTypefaceIndex and set mFontFamily to null

2. When we set the fontFamily property, we will first get the font file via appearance.getFont(). If we can get it, we will assign it to mFontTypeface. The current font alias is obtained using the appearance.getString() method and assigned to mFontFamily

Note: When we set some third-party fonts to fontFamily, the appearance.getFont() method does not get the font

3. When we set the textStyle property, we assign the obtained property value to mTextStyle

The setTypefaceFromAttrs() method is used to set the font in the TextView.

private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
            @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
            @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
    if (typeface == null&& familyName ! =null) {
        // Lookup normal Typeface from system font map.
        final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
        resolveStyleAndSetTypeface(normalTypeface, style, weight);
    } else if(typeface ! =null) {
        resolveStyleAndSetTypeface(typeface, style, weight);
    } else {  // both typeface and familyName is null.
        switch (typefaceIndex) {
            case SANS:
                resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
                break;
            case SERIF:
                resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
                break;
            case MONOSPACE:
                resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
                break;
            case DEFAULT_TYPEFACE:
            default:
                resolveStyleAndSetTypeface(null, style, weight);
                break; }}}Copy the code

The code steps above:

Typeface is empty and familyName is not empty

2. If Typeface is not empty and familyName is empty, typeface is used

3. When typeface and familyName are both empty, the typeface is set to the typefaceIndex value

Typeface, familyName, and typefaceIndex are assigned values in the readTextAppearance method we analyze

5, resolveStyleAndSetTypefce method to font and font style Settings

6. Style is assigned in the readTextAppearance method, which does not conflict with the font setting

Okay, now that we’re done with the code analysis, let’s take a look at the question above, okay? We use a false scheme to derive:

Typeface, familyName, and textStyle are all set in Xml.

TextStyle will definitely work

2. When typeface property is set, typefaceIndex is assigned and familyName is null

If familyName is set, typeface will be assigned a value and familyName will remain empty. Typeface is empty and familyName is assigned if a third-party font is set

Therefore, when we set these three properties, one of Typeface and familyName will never be empty, so the third condition body will not go, and the properties typeface sets will not take effect, while the remaining two properties will take effect

Finally, a summary of these three attributes is made:

1. FontFamily and Typeface properties are used for font Settings. If both are set, the fontFamily and Typeface properties will not take effect

2, textStyle is used for font style Settings, and font Settings will not conflict

The above source code analysis may be a bit convoluted, if you are not clear, please leave a comment to me

Six, TextView set font properties source code analysis

Through the above source code analysis, we know the relationship between fontFamily, Typeface and textStyle. Now let’s take a look at, how do these properties that we set do that? Again to the source analysis link 😂, may be a bit boring, but if you can carefully read, will harvest a lot, dry over

The font properties we set above in Xml or code will eventually go to TextView’s setTypeface overload method:

// Override method 1
public void setTypeface(@Nullable Typeface tf) {
    if(mTextPaint.getTypeface() ! = tf) {// Set the font with mTextPaint
        mTextPaint.setTypeface(tf);
      
      	// Refresh redraw
        if(mLayout ! =null) { nullLayouts(); requestLayout(); invalidate(); }}}// Override method 2
public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
  if (style > 0) {
        if (tf == null) {
            tf = Typeface.defaultFromStyle(style);
        } else {
            tf = Typeface.create(tf, style);
        }
	// Call overload method 1 to set the font
        setTypeface(tf);
      	// Through some algorithms
        inttypefaceStyle = tf ! =null ? tf.getStyle() : 0;
        int need = style & ~typefaceStyle;
      	// Open the brush bold and italicmTextPaint.setFakeBoldText((need & Typeface.BOLD) ! =0); mTextPaint.setTextSkewX((need & Typeface.ITALIC) ! =0 ? -0.25 f : 0);
    } else {
        mTextPaint.setFakeBoldText(false);
        mTextPaint.setTextSkewX(0); setTypeface(tf); }}Copy the code

Analyze the above code:

Override method 1:

TextView sets the font, which is essentially mTextPaint, and mTextPaint is a TextPaint class that inherits from Paint, which is a brush, so the font that we set will actually be drawn by calling the brush method

Override method 2:

Instead of overloading method 1, method 2 passes one more textStyle argument, which is mainly used to mark bold and italic text:

1) If textStyle is set, enter the first condition body. If the tf passed in is null, the Typeface will be fetched based on the style passed in. If the TF is not null, the Typeface will be fetched based on the style passed in. Once the font is set, the brush bold and italic Settings will be turned on

2) If the textStyle is not set, only the font is set and the bold italic Settings are set to false and 0

TextView font Settings and font styles are ultimately done with the brush

Seven,

This article mainly talks about:

1, a general introduction to Android fonts

2. About the three properties that affect Android font display

3. A relationship between textStyle, Typeface and fontFamily

4. How do the three properties set achieve these effects?

You may ask, you have not mentioned the above requirements how to end? I may not be able to fulfill that requirement based on what I’ve told you today. Don’t worry, I’m going to write a series about Android fonts, because there are too many of them. You won’t have to wait too long for this series of articles, as 9 articles are ready for the Denver Nuggets June Challenge 😄

Well, that’s the end of this article, if you have any questions, feel free to leave them in the comments section at 🤝

Thank you for reading this article

Full text here, the original is not easy, welcome to like, collect, comment and forward, your recognition is the power of my creation

Please follow my public account and search sweetying on wechat. Updates will be received as soon as possible