开源项目Plaid学习(三)FontTextView

本章及后续几章主要学习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:

  1. Any attribute values in the given AttributeSet.
  2. The style resource specified in the AttributeSet (named "style").
  3. The default style specified by defStyleAttr.
  4. The default style specified by defStyleRes.
  5. 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写个几十篇没问题啊……感觉就像找到一座金矿,可以挖好久。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容