Drawable 可以方便的作为View的背景使用,也可以做为 ListView 的 divider 等等。在res/drawable下通过xml可以很方便的定义一个Drawable,显然我们的 View 是无法直接使用这个 xml 文件的,它必须先解析成 Drawable 对象才能供我们的 View 显示。那么这个xml文件是如何解析为 Drawable 对象的呢?
Drawable简单使用
在 res/drawable/新建一个 bg.xml
有了这个 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
。