1、概述
在xml中使用自定义Drawable,不用new一个自定义的Drawable对象,在布局中通过@drawable/文件名.xml 直接引用
2、缺陷:
api24才开始支持。如果项目minSdkVersion<24,则这种创建的xml文件只能放在 drawable-v24 目录下。
3、使用方式
创建一个自定义的Drawable
新建一个类,继承Drawable 或它的子类
创建自定义drawable后,在drawable-v24目录下创建xml , 我们取名为custom_drawable.xml。 名字是随意的
<drawable xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
class="com.example.test.drawable.TextDrawable"
app:td_adjustViewBounds="true"
app:td_text="创新层"
app:td_text_color="#ff0"
app:td_text_size_ratio="0.8">
<corners android:radius="3dp" />
<solid android:color="#f0f" />
<size
android:width="60dp"
android:height="30dp" />
</drawable>
或是
<com.example.test.drawable.TextDrawable xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:td_adjustViewBounds="true"
app:td_text="创新层"
app:td_text_color="#ff0"
app:td_text_size_ratio="0.8">
<corners android:radius="3dp" />
<solid android:color="#f0f" />
<size
android:width="60dp"
android:height="30dp" />
</com.example.test.drawable.TextDrawable>
编辑
标签源码解析标签如下
在 DrawableInflater 类中,如果标签名是 drawable,则会获取 class属性,对应的是自定义drawable 完整类名; 如果所有的Drawable标题都没有匹配上,则会直接拿标签名执行 inflateFromClass(name) 方法,通过反射执行无参构靠 方法。所以自定义Drawable中最好有无参构造方法。
public final class DrawableInflater {
@NonNull
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
// Inner classes must be referenced as Outer$Inner, but XML tag names
// can't contain $, so the <drawable> tag allows developers to specify
// the class in an attribute. We'll still run it through inflateFromTag
// to stay consistent with how LayoutInflater works.
if (name.equals("drawable")) {
name = attrs.getAttributeValue(null, "class");
if (name == null) {
throw new InflateException("<drawable> tag must specify class attribute");
}
}
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
@NonNull
@SuppressWarnings("deprecation")
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "adaptive-icon":
return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
case "animated-image":
return new AnimatedImageDrawable();
default:
return null;
}
}
}
引用自定义的drawable资源
使用起来非常方便,就像引用图片资源是一样的。
<View
android:id="@+id/z1"
android:background="@drawable/sg1"
android:layout_width="60dp"
android:layout_height="25dp" />
问题
引用自定义的drawable xml文件,会发现只有第一次引用 custom_drawable.xml时,它会生效,以后就再也不会生效了。debug也发现再次引用时,通过 getResource().getDrawable(R.drawable.custom_drawable) 获取的Drawable对象,居然是自定义Drawable的子类。
编辑
原因:这是因为创建Drawable后有缓存机制,以资源的id 为key,如果再次解析这个资源文件时,就会走到缓存逻辑。 例如 custom_drawable.xml 文件被第二次使用时,它就会走缓存逻辑。存储是一个map, key为资源id,value为资源中对应drawable的 ConstantState对象 . 由它来创建一个新的Drawable对象,但新的drawable对象还是持有原来的 ConstantState 。
ConstantState 对象是用于创建Drawable 和 保存状态的一个抽象类。 以GradientDrawable为例,设置圆角、背景色、边框等参数在解析xml时保存到GradientState类中,GradientState类是GradientDrawable的静态内部类,继承ConstantState抽象类。
getResource().getDrawable()
ResourcesImpl 处理资源,会先到DrawableCache类中找有没有缓存的Drawable.ConstantState对象,如果有,则执行下段代码中 entry.newDrawable(resources,theme) 方法,返回drawable对象。
ResourcesImpl 类
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
cachedDrawable.setChangingConfigurations(value.changingConfigurations);
return cachedDrawable;
}
class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
@UnsupportedAppUsage
DrawableCache() {
}
public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
final Drawable.ConstantState entry = get(key, theme);
if (entry != null) {
//如果有缓存,则通过ConstantState对象执行newDrawable()方法获取一个新的Drawable对象。
return entry.newDrawable(resources, theme);
}
return null;
}
@Override
public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) {
return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
}
}
我们看一下GradientDrawable.GradientState 类,这也就是上面我们提到为什么引用 自定义drawable xml文件时,只有第一次会生效,后面都成了子类对象。原因就是自定Drawable时并没有创建自己的ConstantState,在第一次顺利加载后,第二次从缓存中找ConstantSate对象,最终执行了子类的newDrawable()方法。所以如果要解决这个问题,在自定义Drawable对象时,且需要在xml中使用,则必须创建自己的ConstantState对象。
final static class GradientState extends ConstantState {
@Override
public Drawable newDrawable() {
//通过state对象创建一个GradientDrawable对象
return new GradientDrawable(this, null);
}
@Override
public Drawable newDrawable(@Nullable Resources res) {
// If this drawable is being created for a different density,
// just create a new constant state and call it a day.
final GradientState state;
final int density = Drawable.resolveDensity(res, mDensity);
if (density != mDensity) {
state = new GradientState(this, res);
} else {
state = this;
}
return new GradientDrawable(state, res);
}
...
}
自定义Drawable创建ConstanteState对象
public class KxGradientDrawable extends GradientDrawable {
public boolean mMutated;
private KxGradientState mKxGradientState;
private int mWidth;
private int mHeight;
public KxGradientDrawable() {
this(new KxGradientState(), null);
}
public KxGradientDrawable(KxGradientState state, Resource res) {
super();
mKxGradientState = state;//不能保存,否则不能重新创建
if (mKxGradientState != null) {
mKxGradientState.setDrawable(this);
}
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
//重新创建一个state对象
mKxGradientState = new KxGradientState(mKxGradientState, null);
onUpdateLocalState(null);
mMutated = true;
}
return this;
}
@Override
public final void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) throws IOException, XmlPullParserException {
onInflatte(r, parser, attrs, theme);
super.inflate(r, parser, attrs, theme);
}
public void onInflatte(Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme) {
//记录自定义属性
if (mKxGradientState != null) {
// mKxGradientState
}
}
public void clearMutated() {
mMutated = false;
}
protected void onUpdateLocalState(Resources res) {
}
@Override
public ConstantState getConstantState() {
super.getConstantState();
// mGradientState.mChangingConfigurations = getChangingConfigurations();
return mKxGradientState;
}
public ConstantState getParentState() {
return super.getConstantState();
}
@Override
protected void onBoundsChange(Rect r) {
super.onBoundsChange(r);
int left = r.left;
int right = r.right;
int top = r.top;
int bottom = r.bottom;
mWidth = right - left;
mHeight = bottom - top;
}
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
public static TypedArray obtainAttributes(Resources res,
Resources.Theme theme, AttributeSet set, int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
public static class KxGradientState extends ConstantState {
protected KxGradientDrawable mDrawable;
public KxGradientState() {
}
public KxGradientState(KxGradientState orig, Resource res) {
if (orig != null) {
//记录原成员变量数据 自定义属性等
}
}
@NonNull
@Override
public Drawable newDrawable() {
return new KxGradientDrawable(this,null);
}
@NonNull
@Override
public Drawable newDrawable(@Nullable Resources res) {
return new KxGradientDrawable(this,null);
}
@Override
public int getChangingConfigurations() {
return 0;
}
public void setDrawable(KxGradientDrawable drawable) {
mDrawable = drawable;
}
}
}
备注:
由于系统原生的Drawable的几个子类的ConstantState对象全部被final处理了,所以根本没有办法继承和复写。