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。