美文网首页
res下的 drawable 是如何解析成 Drawable 对

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

作者: leandom | 来源:发表于2016-09-22 19:32 被阅读0次

    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

    相关文章

      网友评论

          本文标题:res下的 drawable 是如何解析成 Drawable 对

          本文链接:https://www.haomeiwen.com/subject/jvgzettx.html