引言:之前涉及到设置view背景的地方几乎都是通过写<shape>
标签的方式实现的。慢慢的,项目里的xml越来越多,命名都成问题了!于是就想用动态设置shape的方式来替换静态配置shape标签。
静态配置shape
这里对形状可绘制对象的描述感觉有点出入,写的是创建ShapeDrawable,但是进入对应的条目后发现是GradientDrawable.
动态设置shape
想要动态配置,首先需要知道在xml中写的<shape>
,<selector>
,<level-list>
等标签的映射对象是什么。这里有个插曲,最开始我也以为<shape>
标签对应的就是ShapeDrawable呢,写的时候发现没法描边(Stroke), 试了两种方案 1.通过设置描边画笔,给paint设置宽度和颜色;2.用LevelListDrawable 通过设置多个层一个填充层,一个描边层来组合。结果这两种方法都不行,后来才去翻的源码。从view.setBackgroundResource()开始追。
Resources.java
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
// 从这里开始加载Drawable
return impl.loadDrawable(this, value, id, theme, true);
} finally {
releaseTempTypedValue(value);
}
}
ResourcesImpl.java
@Nullable
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
boolean useCache) throws NotFoundException {
try {
......
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
// 从xml或者资源流中加载Drawable
dr = loadDrawableForCookie(wrapper, value, id, null);
}
return dr;
} catch (Exception e) {
......
}
}
/**
* Loads a drawable from XML or resources stream.
* 从xml或者数据流中加载Drawable
*/
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme) {
final String file = value.string.toString();
......
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
if (file.endsWith(".xml")) {
// ok,我们看下这里,通过解析器把xml文件解析成Drawable对象
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(wrapper, rp, theme);
rp.close();
} else {
// 流的解析
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
} catch (Exception e) {
......
}
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return dr;
}
Drawable.java
// 使用可选的theme从XML文档中创建drawable对象。
public static Drawable createFromXml(Resources r, XmlPullParser parser, Theme theme)
throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
......
// 从xml内部以指定主题创建一个Drawable对象
Drawable drawable = createFromXmlInner(r, parser, attrs, theme);
if (drawable == null) {
throw new RuntimeException("Unknown initial tag: " + parser.getName());
}
return drawable;
}
// 从xml内部以指定主题创建一个Drawable对象
public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
return r.getDrawableInflater().inflateFromXml(parser.getName(), parser, attrs, theme);
}
追到这里发现是从Resources 中调用了一个方法。在Resources.java 中搜索getDrawableInflater()这个方法,出现了DrawableInflater这个类。
Resources.java
// 该inflater用于创建Drawable对象
public final DrawableInflater getDrawableInflater() {
if (mDrawableInflater == null) {
mDrawableInflater = new DrawableInflater(this, mClassLoader);
}
return mDrawableInflater;
}
没法直接追下去了,既然这样我们在源码中直接搜索DrawableInflater.java这个类,然后查看它里面的inflateFromXml() 方法。(注:我这里的源码版本是android-25)
DrawableInflater.java
@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 "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();
default:
return null;
}
}
到这里总该明白与xml中的标签(shape等标签)相对应的对象是哪些了吧。项目中用的最多的就是圆角矩形背景了,然后有的会有描边,还有选择器的效果。我们就用GradientDrawable 来实现。GradientDrawable可以用来设置shape类型、shape填充色、描边色和矩形的边角弧度。
动态设置shape代码
以圆角矩形为例:
/**
* 获得一个指定填充色,边框宽度、颜色的圆角矩形drawable。
* Android 中 在xml中写的"shape"标签映射对象就是GradientDrawable。
* 通过设置solidColors 和strokeColors 可实现选择器的效果
*
* @param solidColors 填充色
* @param strokeColors 描边色
* @param strokeWidth 描边线宽度
* @param dashWidth 虚线(破折线)的长度(以像素为单位)
* @param dashGap 虚线(破折线)间距,当dashGap=0dp时,为实线
* @param radius 圆角角度
* @return GradientDrawable
*/
public static Drawable getShapeDrawable(ColorStateList solidColors,
ColorStateList strokeColors, int strokeWidth,
float dashWidth, float dashGap,
float radius) {
GradientDrawable gradientDrawable = new GradientDrawable();
gradientDrawable.setShape(GradientDrawable.RECTANGLE);
gradientDrawable.setCornerRadius(radius);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
gradientDrawable.setColor(solidColors);
//显示一条虚线,破折线的宽度为dashWith,破折线之间的空隙的宽度为dashGap,当dashGap=0dp时,为实线
gradientDrawable.setStroke(strokeWidth, strokeColors, dashWidth, dashGap);
} else {
gradientDrawable.setColor(solidColors.getDefaultColor());
//显示一条虚线,破折线的宽度为dashWith,破折线之间的空隙的宽度为dashGap,当dashGap=0dp时,为实线
gradientDrawable.setStroke(strokeWidth, strokeColors.getDefaultColor(), dashWidth, dashGap);
}
return gradientDrawable;
}
/**
* 获得一个指定填充色,指定描边色的圆角矩形drawable
*
* @param solidColor 填充色
* @param strokeColor 描边色
* @param strokeWidth 描边线宽度
* @param dashWidth 虚线(破折线)宽度
* @param dashGap 虚线(破折线)间距,当dashGap=0dp时,为实线
* @param radius 圆角角度
* @return GradientDrawable
*/
public static Drawable getShapeDrawable(@ColorInt int solidColor,
@ColorInt int strokeColor, int strokeWidth,
float dashWidth, float dashGap,
float radius) {
return getShapeDrawable(ColorStateList.valueOf(solidColor),
ColorStateList.valueOf(strokeColor), strokeWidth, dashWidth, dashGap,
radius);
}
/**
* 获得一个选择器Drawable.
* Android 中 在xml中写的"selector"标签映射对象就是StateListDrawable 对象
*
* @param defaultDrawable 默认时显示的Drawable
* @param pressedDrawable 按下时显示的Drawable
* @return 选择器Drawable
*/
public static StateListDrawable getSelectorDrawable(Drawable defaultDrawable, Drawable pressedDrawable) {
if (defaultDrawable == null) return null;
if (pressedDrawable == null) pressedDrawable = defaultDrawable;
int[][] state = {{-android.R.attr.state_pressed}, {android.R.attr.state_pressed}};
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(state[0], defaultDrawable);
stateListDrawable.addState(state[1], pressedDrawable);
return stateListDrawable;
}
/**
* 获得一个选择器Drawable.
* Android 中 在xml中写的"selector"标签映射对象就是StateListDrawable 对象
*
* @param defaultColor 默认时显示的颜色
* @param pressedColor 按下时显示的颜色
* @return 选择器Drawable
*/
public static StateListDrawable getSelectorDrawable(int defaultColor, int pressedColor, float radius) {
Drawable defaultDrawable = getSolidShapeDrawable(defaultColor, radius);
Drawable pressedDrawable = getSolidShapeDrawable(pressedColor, radius);
return getSelectorDrawable(defaultDrawable, pressedDrawable);
}