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 對象夺巩。如果是贞让,就應該重點關注。
根據(jù)這個規(guī)則猜測 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"))
條件成立征绎,接著執(zhí)行loadXmlResourceParser
去獲取一個xml Parser解析器蹲姐,注意到這里傳入了我們的 id磨取。緊接著執(zhí)行/*Drawable*/ dr = Drawable.createFromXml(/*Resources*/this, rp, theme)
方法。這是一個靜態(tài)方法柴墩,返回的是 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)
歼指,繼續(xù)跟進:
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final Drawable drawable;
// drawable.xml 下的跟節(jié)點
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定義的根節(jié)點,根據(jù)根節(jié)點構造出相應的 Drawable對象踩身,然后調用drawable.inflate(r, parser, attrs, theme)
方法把xml定義的一些屬性設置到drawable對象上胀茵。如果 Drawable 的子類有自己的屬性,那么就可以重寫 inflate 這個方法來解析特有的屬性挟阻。
另外琼娘,注意到,在Drawable.createFromXmlInner
方法附鸽,發(fā)現(xiàn)我們在xml 定義的 shape 實際上是 GradientDrawable
脱拼,而不是 ShapeDrawable
。