Android 動態設定Shape
阿新 • • 發佈:2019-01-07
引言:之前涉及到設定view背景的地方几乎都是通過寫<shape>
標籤的方式實現的。慢慢的,專案裡的xml越來越多,命名都成問題了!於是就想用動態設定shape的方式來替換靜態配置shape標籤。
靜態配置shape
這裡對形狀可繪製物件的描述感覺有點出入,寫的是建立ShapeDrawable,但是進入對應的條目後發現是GradientDrawable.
動態設定shape
想要動態配置,首先需要知道在xml中寫的<shape>
,<selector>
,<level-list>
等標籤的對映物件是什麼。這裡有個插曲,最開始我也以為<shape>
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);
}