res下的 drawable 是如何解析成 Drawable 對(duì)象?

Drawable 可以方便的作為View的背景使用,也可以做為 ListView 的 divider 等等。在res/drawable下通過(guò)xml可以很方便的定義一個(gè)Drawable,顯然我們的 View 是無(wú)法直接使用這個(gè) xml 文件的,它必須先解析成 Drawable 對(duì)象才能供我們的 View 顯示。那么這個(gè)xml文件是如何解析為 Drawable 對(duì)象的呢?

Drawable簡(jiǎn)單使用

在 res/drawable/新建一個(gè) bg.xml

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

有了這個(gè) bg.xml 我們就可以為 View 指定一個(gè)background屬性了,當(dāng)然也可以在 java 代碼中設(shè)置:

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

實(shí)際上在 layout 中指定 background屬性最終也會(huì)走上面的代碼。接下來(lái)分析下Resources#getDrawable(int id)這個(gè)方法。這個(gè)方法負(fù)責(zé)將給定資源 id 的 drawable 文件解析成 Drawable 對(duì)象。

Resources#getDrawable(int id)

Resources#getDrawable(int id) 最終會(huì)調(diào)用 getDrawable(int id,Theme theme) ,我們看下這個(gè)方法:

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, 重點(diǎn)關(guān)注
    final Drawable res = loadDrawable(value, id, theme);
    synchronized (mAccessLock) {
        if (mTmpValue == null) {
            mTmpValue = value;
        }
    }
    return res;
}

首先 getValue(value, id, theme)方法先檢查指定id的xml文件是否存在。這個(gè)方法可能會(huì)對(duì)TypeValue進(jìn)行一些賦值。比如后面用到的 typeValue.string應(yīng)該就是制定id的文件名(帶后綴的)。

然后調(diào)用loadDrawable(value, id, theme)去獲取 Drawable對(duì)象。
顯然重點(diǎn)方法是loadDrawable(value, id, theme)。跟進(jìn)去這個(gè)方法:

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);
    }
    // 省略...
}

省略部分源碼,我們重點(diǎn)關(guān)注 id 傳入哪個(gè)方法,該方法返回值是不是 Drawable 對(duì)象。如果是,就應(yīng)該重點(diǎn)關(guān)注。

根據(jù)這個(gè)規(guī)則猜測(cè) loadDrawableForCookie 可能是我們想要尋找的方法,跟進(jìn)去看下:

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;
}

重點(diǎn)在 try 語(yǔ)句,if (file.endsWith(".xml"))條件成立,接著執(zhí)行loadXmlResourceParser去獲取一個(gè)xml Parser解析器,注意到這里傳入了我們的 id。緊接著執(zhí)行/*Drawable*/ dr = Drawable.createFromXml(/*Resources*/this, rp, theme) 方法。這是一個(gè)靜態(tài)方法,返回的是 Drawable 對(duì)象,并且這個(gè) Drawable 最終會(huì)作為 loadDrawableForCookie 的返回值,然后一步一步返回到最開(kāi)始的Resources#getDrawable方法。到此,我們就知道Drawable.createFromXml(Resources r, XmlPullParser parser, Theme theme) 完成了 Drawable 對(duì)象的解析工作。趕緊跟進(jìn)去看下:

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;
}

重點(diǎn)在createFromXmlInner(r, parser, attrs, theme),繼續(xù)跟進(jìn):

public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
        Theme theme) throws XmlPullParserException, IOException {
    final Drawable drawable;
    // drawable.xml 下的跟節(jié)點(diǎn)
    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;
}

到這里瞬間恍然大悟。

這個(gè)方法會(huì)獲取xml定義的根節(jié)點(diǎn),根據(jù)根節(jié)點(diǎn)構(gòu)造出相應(yīng)的 Drawable對(duì)象,然后調(diào)用drawable.inflate(r, parser, attrs, theme)方法把xml定義的一些屬性設(shè)置到drawable對(duì)象上。如果 Drawable 的子類(lèi)有自己的屬性,那么就可以重寫(xiě) inflate 這個(gè)方法來(lái)解析特有的屬性。

另外,注意到,在Drawable.createFromXmlInner方法,發(fā)現(xiàn)我們?cè)趚ml 定義的 shape 實(shí)際上是 GradientDrawable,而不是 ShapeDrawable。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,146評(píng)論 25 708
  • //通過(guò)獲得資源文件進(jìn)行設(shè)置。根據(jù)不同的情況R.color.red也可以是R.string.red或者R.draw...
    gogoingmonkey閱讀 2,040評(píng)論 0 2
  • 1、Drawable 簡(jiǎn)介 Drawable——可簡(jiǎn)單理解為可繪制物,表示一些可以繪制在 Canvas 上的對(duì)象。...
    牧秦丶閱讀 15,363評(píng)論 0 15
  • 沒(méi)關(guān)系,就這樣離去,,,,, 在火車(chē)站,遇到一個(gè)姑娘,穿著厚厚的羽絨服,一雙紅色的馬丁靴,應(yīng)該是在打車(chē)。面對(duì)...
    人花你們選閱讀 277評(píng)論 0 1
  • 離春節(jié)的日子越來(lái)越近了,陪我大半年的媽媽要回老家過(guò)年了。突然很不舍。 晚上,我練瑜伽,媽媽就坐在床頭看著,默默的,...
    小貓說(shuō)法閱讀 321評(píng)論 0 4

友情鏈接更多精彩內(nèi)容