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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值