res下的 drawable 是如何解析成 Drawable 对象?

Drawable 可以方便的作为View的背景使用,也可以做为 ListView 的 divider 等等。在res/drawable下通过xml可以很方便的定义一个Drawable,显然我们的 View 是无法直接使用这个 xml 文件的,它必须先解析成 Drawable 对象才能供我们的 View 显示。那么这个xml文件是如何解析为 Drawable 对象的呢?

Drawable简单使用

在 res/drawable/新建一个 bg.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/black" />
</shape>

有了这个 bg.xml 我们就可以为 View 指定一个background属性了,当然也可以在 java 代码中设置:

Drawable d = getResources().getDrawable(R.drawable.bg);
view.setBackground(d);

实际上在 layout 中指定 background属性最终也会走上面的代码。接下来分析下Resources#getDrawable(int id)这个方法。这个方法负责将给定资源 id 的 drawable 文件解析成 Drawable 对象。

Resources#getDrawable(int id)

Resources#getDrawable(int id) 最终会调用 getDrawable(int id,Theme theme) ,我们看下这个方法:

public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
    TypedValue value;
    synchronized (mAccessLock) {
        value = mTmpValue;
        if (value == null) {
            value = new TypedValue();
        } else {
            mTmpValue = null;
        }
        getValue(id, value, true);
    }
    // 传入 id, 返回 Drawable, 重点关注
    final Drawable res = loadDrawable(value, id, theme);
    synchronized (mAccessLock) {
        if (mTmpValue == null) {
            mTmpValue = value;
        }
    }
    return res;
}

首先 getValue(value, id, theme)方法先检查指定id的xml文件是否存在。这个方法可能会对TypeValue进行一些赋值。比如后面用到的 typeValue.string应该就是制定id的文件名(带后缀的)。

然后调用loadDrawable(value, id, theme)去获取 Drawable对象。
显然重点方法是loadDrawable(value, id, theme)。跟进去这个方法:

Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
    // 省略...
    Drawable dr;
    if (cs != null) {
        dr = cs.newDrawable(this);
    } else if (isColorDrawable) {
        dr = new ColorDrawable(value.data);
    } else {
        dr = loadDrawableForCookie(value, id, null);
    }
    // 省略...
}

省略部分源码,我们重点关注 id 传入哪个方法,该方法返回值是不是 Drawable 对象。如果是,就应该重点关注。

根据这个规则猜测 loadDrawableForCookie 可能是我们想要寻找的方法,跟进去看下:

private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
    // value.string: xml 文件名
    if (value.string == null) {
        throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
                + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
    }

    final String file = value.string.toString();

    // false ,不看
    if (TRACE_FOR_MISS_PRELOAD) {
        // Log only framework resources
        if ((id >>> 24) == 0x1) {
            final String name = getResourceName(id);
            if (name != null) {
                Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
                        + ": " + name + " at " + file);
            }
        }
    }

    if (DEBUG_LOAD) {
        Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
    }

    final Drawable dr;

    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
    try {
        // file 为我们的 drawable 文件,比如 bg.xml
        if (file.endsWith(".xml")) {
            final XmlResourceParser rp = loadXmlResourceParser(
                    file, id, value.assetCookie, "drawable");
            dr = Drawable.createFromXml(this, rp, theme);
            rp.close();
        } else {
            final InputStream is = mAssets.openNonAsset(
                    value.assetCookie, file, AssetManager.ACCESS_STREAMING);
            dr = Drawable.createFromResourceStream(this, value, is, file, null);
            is.close();
        }
    } catch (Exception e) {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        final NotFoundException rnf = new NotFoundException(
                "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
        rnf.initCause(e);
        throw rnf;
    }
    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

    return dr;
}

重点在 try 语句,if (file.endsWith(".xml"))条件成立,接着执行loadXmlResourceParser去获取一个xml Parser解析器,注意到这里传入了我们的 id。紧接着执行/*Drawable*/ dr = Drawable.createFromXml(/*Resources*/this, rp, theme) 方法。这是一个静态方法,返回的是 Drawable 对象,并且这个 Drawable 最终会作为 loadDrawableForCookie 的返回值,然后一步一步返回到最开始的Resources#getDrawable方法。到此,我们就知道Drawable.createFromXml(Resources r, XmlPullParser parser, Theme theme) 完成了 Drawable 对象的解析工作。赶紧跟进去看下:

public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme)
        throws XmlPullParserException, IOException {
    AttributeSet attrs = Xml.asAttributeSet(parser);

    int type;
    while ((type=parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
        // Empty loop
    }

    if (type != XmlPullParser.START_TAG) {
        throw new XmlPullParserException("No start tag found");
    }

    Drawable drawable = createFromXmlInner(r, parser, attrs, theme);

    if (drawable == null) {
        throw new RuntimeException("Unknown initial tag: " + parser.getName());
    }

    return drawable;
}

重点在createFromXmlInner(r, parser, attrs, theme),继续跟进:

public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
        Theme theme) throws XmlPullParserException, IOException {
    final Drawable drawable;
    // drawable.xml 下的跟节点
    final String name = parser.getName();
    switch (name) {
        case "selector":
            drawable = new StateListDrawable();
            break;
        case "animated-selector":
            drawable = new AnimatedStateListDrawable();
            break;
        case "level-list":
            drawable = new LevelListDrawable();
            break;
        case "layer-list":
            drawable = new LayerDrawable();
            break;
        case "transition":
            drawable = new TransitionDrawable();
            break;
        case "ripple":
            drawable = new RippleDrawable();
            break;
        case "color":
            drawable = new ColorDrawable();
            break;
        case "shape":
            drawable = new GradientDrawable();
            break;
        case "vector":
            drawable = new VectorDrawable();
            break;
        case "animated-vector":
            drawable = new AnimatedVectorDrawable();
            break;
        case "scale":
            drawable = new ScaleDrawable();
            break;
        case "clip":
            drawable = new ClipDrawable();
            break;
        case "rotate":
            drawable = new RotateDrawable();
            break;
        case "animated-rotate":
            drawable = new AnimatedRotateDrawable();
            break;
        case "animation-list":
            drawable = new AnimationDrawable();
            break;
        case "inset":
            drawable = new InsetDrawable();
            break;
        case "bitmap":
            drawable = new BitmapDrawable();
            break;
        case "nine-patch":
            drawable = new NinePatchDrawable();
            break;
        default:
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": invalid drawable tag " + name);

    }
    drawable.inflate(r, parser, attrs, theme);
    return drawable;
}

到这里瞬间恍然大悟。

这个方法会获取xml定义的根节点,根据根节点构造出相应的 Drawable对象,然后调用drawable.inflate(r, parser, attrs, theme)方法把xml定义的一些属性设置到drawable对象上。如果 Drawable 的子类有自己的属性,那么就可以重写 inflate 这个方法来解析特有的属性。

另外,注意到,在Drawable.createFromXmlInner方法,发现我们在xml 定义的 shape 实际上是 GradientDrawable,而不是 ShapeDrawable

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • //通过获得资源文件进行设置。根据不同的情况R.color.red也可以是R.string.red或者R.draw...
    gogoingmonkey阅读 1,928评论 0 2
  • 1、Drawable 简介 Drawable——可简单理解为可绘制物,表示一些可以绘制在 Canvas 上的对象。...
    牧秦丶阅读 14,657评论 0 15
  • 没关系,就这样离去,,,,, 在火车站,遇到一个姑娘,穿着厚厚的羽绒服,一双红色的马丁靴,应该是在打车。面对...
    人花你们选阅读 254评论 0 1
  • 离春节的日子越来越近了,陪我大半年的妈妈要回老家过年了。突然很不舍。 晚上,我练瑜伽,妈妈就坐在床头看着,默默的,...
    小猫说法阅读 283评论 0 4