Custom Widget中通过AttributeSet读取xml布局中定义的TypeFace

从API 26开始,Android允许直接在xml布局文件中引用res/font/下的字体文件或者font-family来为TextView设置TypeFace。做法是只需要在TextView的标签中插入android:fontFamily=”@font/lobster”,就可以将res/font/lobster.xml这个font-family或者res/font/lobster.ttf这个字体文件设置为该TextView的TypeFace。

为了提供向后兼容性,使用Support Library 26还可以将该特性支持到API 16,具体见:https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml

那在我们的自定义控件中如何像TextView一样使用xml定义TypeFace呢?

本文的做法实现了在API 26及以上能够直接使用android:fontFamily=”@font/lobster”的方法将res/font/lobster.xml这个font-family或者res/font/lobster.ttf这个字体文件所定义的TypeFace传入自定义控件使用,并能解析android:textStyle从font-family中取出粗体斜体等正确的子字体。针对未传入android:fontFamily的情况还允许使用android:typeface简单粗暴定义自带的sans、serif、monospace三种风格字体。不过在API 26以下无法使用res/font/下的font-family或者字体文件。

效果图:

假设我们的Custom Widget叫做RulerView,我们要为它下方的灰色刻度值的文字添加自定义字体。

attrs

首先在attrs.xml中配置属性。这里直接使用Android自带的fontFamily、typeface、textStyle这三个属性,所以在属性名前添加“android:”

<declare-styleable name="RulerView">
    <attr name="android:fontFamily" />
    <attr name="android:typeface">
        <enum name="normal" value="0" />
        <enum name="sans" value="1" />
        <enum name="serif" value="2" />
        <enum name="monospace" value="3" />
    </attr>
    <attr name="android:textStyle" />
</declare-styleable>

java

定义属性变量

private String familyName;
private int typefaceIndex;
private int styleIndex;
private Typeface textTypeFace;

private static final int SANS = 1;
private static final int SERIF = 2;
private static final int MONOSPACE = 3;

解析AttributeSet,针对API 26及以上,我们先尝试将传入的fontFamily当做res/font/下的font-family来解析,这时候不需要解析fontFamily这个属性了。如果失败,或者针对API 26以下,仍然按照之前的方法去解析fontFamily。之后继续解析typeface、textStyle这两个属性。

private void initAttrs(Context context, AttributeSet attrs) {
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RulerView);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        try {
            textTypeFace = ta.getFont(R.styleable.RulerView_android_fontFamily);
        } catch (UnsupportedOperationException | Resources.NotFoundException e) {
            // Expected if it is not a font resource.
        }
    }
    if (textTypeFace == null) {
        familyName = ta.getString(R.styleable.RulerView_android_fontFamily);
    }
    typefaceIndex = ta.getInt(R.styleable.RulerView_android_typeface, -1);
    styleIndex = ta.getInt(R.styleable.RulerView_android_textStyle, -1);
    ta.recycle();
}

生成TypeFace。针对成功解析出textTypeFace的情况,配合styleIndex得到最终TypeFace,否则使用typefaceIndex结合styleIndex得到TypeFace。

/**
 * Sets the Typeface taking into account the given attributes.
 *
 * @param familyName family name string, e.g. "serif"
 * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
 * @param styleIndex a typeface style
 */
private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
    Typeface tf = textTypeFace;
    if (tf != null) {
        setTypeface(tf, styleIndex);
        return;
    }
    if (familyName != null) {
        tf = Typeface.create(familyName, styleIndex);
        if (tf != null) {
            setTypeface(tf);
            return;
        }
    }
    switch (typefaceIndex) {
        case SANS:
            tf = Typeface.SANS_SERIF;
            break;
        case SERIF:
            tf = Typeface.SERIF;
            break;
        case MONOSPACE:
            tf = Typeface.MONOSPACE;
            break;
    }
    setTypeface(tf, styleIndex);
}

/**
 * Sets the typeface and style in which the text should be displayed.
 * Note that not all Typeface families actually have bold and italic
 * variants, so you may need to use
 * {@link #setTypeface(Typeface, int)} to get the appearance
 * that you actually want.
 */
public void setTypeface(Typeface tf) {
    if (mTextPaint.getTypeface() != tf) {
        mTextPaint.setTypeface(tf);
    }
}

/**
 * Sets the typeface and style in which the text should be displayed,
 * and turns on the fake bold and italic bits in the Paint if the
 * Typeface that you provided does not have all the bits in the
 * style that you specified.
 */
public void setTypeface(Typeface tf, int style) {
    if (style > 0) {
        if (tf == null) {
            tf = Typeface.defaultFromStyle(style);
        } else {
            tf = Typeface.create(tf, style);
        }
        setTypeface(tf);
        // now compute what (if any) algorithmic styling is needed
        int typefaceStyle = tf != null ? tf.getStyle() : 0;
        int need = style & ~typefaceStyle;
        mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
        mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    } else {
        mTextPaint.setFakeBoldText(false);
        mTextPaint.setTextSkewX(0);
        setTypeface(tf);
    }
}

设置TypeFace

setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);

TODO

研究Support Library 26中是如何让API 26以下能够使用res/font/下的font-family

参考文献

https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml

https://android.googlesource.com/platform/frameworks/base/+/refs/heads/pie-r2-release/core/java/android/widget/TextView.java

https://android.googlesource.com/platform/frameworks/base/+/jb-mr0-release/core/java/android/widget/TextView.java

Share

You may also like...

1 Response

回复 邵彬 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注