本章及后续几章主要学习Plaid里面一系列自定义的Textview。大体来说,是这样:
TextView->FontTextView->BaselineGridTextView->AuthorTextView
...BaselineGridTextView->DynamicTypeTextView
FontTextView
/**
* Extension to TextView that adds support for custom fonts.
*/
public class FontTextView extends TextView {
public FontTextView(Context context) {
super(context);
init(context, null);
}
public FontTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FontTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public FontTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FontTextView);
if (a.hasValue(R.styleable.FontTextView_android_textAppearance)) {
final int textAppearanceId = a.getResourceId(R.styleable
.FontTextView_android_textAppearance,
android.R.style.TextAppearance);
TypedArray atp = getContext().obtainStyledAttributes(textAppearanceId,
R.styleable.FontTextAppearance);
if (atp.hasValue(R.styleable.FontTextAppearance_font)) {
setFont(atp.getString(R.styleable.FontTextAppearance_font));
}
atp.recycle();
}
if (a.hasValue(R.styleable.FontTextView_font)) {
setFont(a.getString(R.styleable.FontTextView_font));
}
a.recycle();
}
public void setFont(String font) {
setPaintFlags(getPaintFlags() | Paint.ANTI_ALIAS_FLAG);
setTypeface(FontUtil.get(getContext(), font));
}
}
这个Custome View只做一件事情,就是支持设置字体。此外,还需要两个res/values目录下的xml文件来自定义属性(主要是font属性要处理,至于textview的基础属性,可以直接用,因为父类会自己提取然后处理):
attrs_font.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="font" format="string" />
<declare-styleable name="FontTextAppearance">
<attr name="android:textSize" />
<attr name="android:textColor" />
<attr name="android:fontFamily" />
<attr name="font" />
</declare-styleable>
</resources>
attrs_font_text_view.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FontTextView">
<attr name="font" />
<attr name="android:textAppearance" />
</declare-styleable>
</resources>
再回过来看FontTextView的代码,可以看到有4个构造函数,参数是Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes这几个,逐渐增多。
查阅资料可知,第四个初始函数是API21加上的,参考官方文档:
public View (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
Added in API level 21
Perform inflation from XML and apply a class-specific base style from a theme attribute or style resource. This constructor of View allows subclasses to use their own base style when they are inflating.
When determining the final value of a particular attribute, there are four inputs that come into play:
- Any attribute values in the given AttributeSet.
- The style resource specified in the AttributeSet (named "style").
- The default style specified by defStyleAttr.
- The default style specified by defStyleRes.
- The base values in this theme.
Each of these inputs is considered in-order, with the first listed taking precedence over the following ones. In other words, if in the AttributeSet you have supplied , then the button's text will always be black, regardless of what is specified in any of the styles.
Parameters
- context: The Context the view is running in, through which it can access the current theme, resources, etc.
- attrs: The attributes of the XML tag that is inflating the view.
- defStyleAttr: An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
- defStyleRes: A resource identifier of a style resource that supplies default values for the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
文档说的很清楚,def在这里的意思是default,也就是指定默认的属性,如果没有的话就用0。
这里的代码并没有设置默认style,所以其实构造都是用的init函数,这也是很多自定义view为了省事采用的方法,毕竟自己用不需要搞的太麻烦。
这里自定义的方式也值得注意,并没有放在一个xml文件,而是用了两个xml文件。attrs_font_text_view.xml这个文件比较好理解,就是说这个控件有font和textAppearance两种属性。带有android的属性,就是支持这个属性的意思,可以直接用而不需要自己管。
光从这个FontTextView的角度来看,一个attrs_font_text_view.xml已经够了,为什么要再弄一个attrs_font.xml呢?
init函数代码分析:先是读FontTextView的属性,然后判断是不是有android:textAppearance属性,假如有,那么读取资源,然后再套到FontTextAppearance里,然后再提取font设置一次。之后再直接用FontTextView提取font设置一次。
因此,FontTextAppearance并不会直接出现在其他地方,唯一的作用是可以把font写到style里面。
例如:
<FontTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is fonttttt!"
android:textAppearance="@style/textAppearance" />
然后在styles里面:
<style name="textAppearance">
<item name="android:textColor">@color/colorPrimary</item>
<item name="android:textSize">20sp</item>
<item name="font">roboto-mono-regular</item>
</style>
这么折腾也是醉了。换我的话直接加个font属性就行了。
字体是通过FontUtil来查找的:
/**
* Adapted from github.com/romannurik/muzei/
* <p/>
* Also see https://code.google.com/p/android/issues/detail?id=9904
*/
public class FontUtil {
private FontUtil() { }
private static final Map<String, Typeface> sTypefaceCache = new HashMap<>();
public static Typeface get(Context context, String font) {
synchronized (sTypefaceCache) {
if (!sTypefaceCache.containsKey(font)) {
Typeface tf = Typeface.createFromAsset(
context.getApplicationContext().getAssets(), "fonts/" + font + ".ttf");
sTypefaceCache.put(font, tf);
}
return sTypefaceCache.get(font);
}
}
public static String getName(@NonNull Typeface typeface) {
for (Map.Entry<String, Typeface> entry : sTypefaceCache.entrySet()) {
if (entry.getValue() == typeface) {
return entry.getKey();
}
}
return null;
}
}
这个工具类也很简单,就是实现字符映射对应的字体库。
小结
本来想挑一个最简单的自定义View,没想到写下来也这么长了。不过麻雀虽小,五脏俱全。这个View看似简单,却包括了一些很重要的知识,尤其是自定义属性的处理。
按照目前这个进度,Plaid这个App写个几十篇没问题啊……感觉就像找到一座金矿,可以挖好久。