从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
这里有一个兼容 API < 26 的方法 https://stackoverflow.com/questions/45339115/get-font-resource-from-typedarray-before-api-26?rq=1