从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