Android shape標(biāo)簽使用方法介紹
作為Android開(kāi)發(fā),shape標(biāo)簽的使用定然不陌生。
shape標(biāo)簽基本使用語(yǔ)法
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"] >
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<gradient
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:useLevel=["true" | "false"] />
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size
android:width="integer"
android:height="integer" />
<solid
android:color="color" />
<stroke
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>shape標(biāo)簽可用于各種背景繪制,然而每需要一個(gè)新的背景,即使只有細(xì)微的改動(dòng),諸如一個(gè)角度的改變、顏色的改變,都需要重新創(chuàng)建一個(gè)xml文件以配置新背景的shape標(biāo)簽。
通過(guò)了解shape標(biāo)簽是如何進(jìn)行背景繪制的,就可以后續(xù)進(jìn)行自定義屬性開(kāi)發(fā)來(lái)解放大量shape標(biāo)簽下的xml文件的創(chuàng)建。
Shape標(biāo)簽生成GradientDrawable對(duì)象
首先來(lái)了解一下,shape標(biāo)簽下的xml文件是如何最終被解析為GradientDrawable對(duì)象。
View對(duì)象的background屬性最終是一個(gè)Drawable對(duì)象,shape標(biāo)簽下的xml文件也是被賦予給了background屬性,最終也是生成了一個(gè)Drawable對(duì)象。
在View的構(gòu)造函數(shù)中可看到是通過(guò)TypedArray.getDrawable獲得Drawable對(duì)象賦予background屬性。
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
....
background = a.getDrawable(attr);
....
}
追蹤下去,Resources.loadDrawable -> ResourcesImpl.loadDrawable -> ResourcesImpl.loadXmlDrawable。
因?yàn)槭窃趚ml文件中定義,因此必然需要一個(gè)xml解析器進(jìn)行解析。在此處就獲取了一個(gè)XmlResourceParser,然后傳入Drawable.createFromXmlForDensity。
private Drawable loadXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
int id, int density, String file)
throws IOException, XmlPullParserException {
try (
XmlResourceParser rp =
loadXmlResourceParser(file, id, value.assetCookie, "drawable")
) {
return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
}
}
平時(shí)解析layout文件的時(shí)候經(jīng)常會(huì)使用LayoutInflater,那么Drawable是否也存在對(duì)應(yīng)的DrawableInflater呢?繼續(xù)往下走,就會(huì)發(fā)現(xiàn)答案是肯定的。
@NonNull
static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
@Nullable Theme theme) throws XmlPullParserException, IOException {
return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
density, theme);
}
此處通過(guò)Resources.getDrawableInflater獲取到DrawableInflater,接著就使用DrawableInflater的inflateFromXmlForDensity方法進(jìn)行解析。
@NonNull
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
....
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
在DrawableInflater的inflateFromXmlForDensity方法中可以看見(jiàn),通過(guò)inflateFromTag方法,生成了Drawable對(duì)象,并最終將其返回,那么shape標(biāo)簽生成GradientDrawable對(duì)象的邏輯就在該方法內(nèi)了。
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;
}
}一目了然,通過(guò)不同的標(biāo)簽名字生成相應(yīng)的Drawable對(duì)象。shape標(biāo)簽生成GradientDrawable對(duì)象,selector標(biāo)簽生成StateListDrawable對(duì)象。
GradientDrawable獲取shape子標(biāo)簽屬性
看GradientDrawable必然要先看GradientState。
每一個(gè)Drawable的子類,都會(huì)有一個(gè)繼承于ConstantState的內(nèi)部靜態(tài)類,它里面所聲明的屬性肯定都是這一個(gè)子類Drawable中獨(dú)有的。
final static class GradientState extends ConstantState {
public @Shape int mShape = RECTANGLE;
public ColorStateList mSolidColors;
public ColorStateList mStrokeColors;
public int mStrokeWidth = -1;
public float mStrokeDashWidth = 0.0f;
public float mRadius = 0.0f;
public float[] mRadiusArray = null;
....
}
@IntDef({RECTANGLE, OVAL, LINE, RING})
@Retention(RetentionPolicy.SOURCE)
public @interface Shape {}
可以看到Shape定義了四個(gè)值的取值范圍。那么GradientState里的這些屬性又是怎么獲取的呢?
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
mGradientState.setDensity(Drawable.resolveDensity(r, 0));
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
updateStateFromTypedArray(a);
a.recycle();
inflateChildElements(r, parser, attrs, theme);
updateLocalState(r);
}
在GradientDrawable.inflate里,通過(guò)inflateChildElements就能獲取到各個(gè)子標(biāo)簽屬性了。
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
TypedArray a;
int type;
....
String name = parser.getName();
if (name.equals("size")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
updateGradientDrawableSize(a);
a.recycle();
} else if (name.equals("gradient")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
updateGradientDrawableGradient(r, a);
a.recycle();
} else if (name.equals("solid")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
updateGradientDrawableSolid(a);
a.recycle();
} else if (name.equals("stroke")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
updateGradientDrawableStroke(a);
a.recycle();
} else if (name.equals("corners")) {
a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
updateDrawableCorners(a);
a.recycle();
} else if (name.equals("padding")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
updateGradientDrawablePadding(a);
a.recycle();
} else {
Log.w("drawable", "Bad element under <shape>: " + name);
}
}
}看到了在寫shape標(biāo)簽下的xml文件時(shí),熟悉的"corners"、“solid”、“gradient”。
以"corners"為例:
private void updateDrawableCorners(TypedArray a) {
final GradientState st = mGradientState;
// Account for any configuration changes.
st.mChangingConfigurations |= a.getChangingConfigurations();
// Extract the theme attributes, if any.
st.mAttrCorners = a.extractThemeAttrs();
final int radius = a.getDimensionPixelSize(
R.styleable.DrawableCorners_radius, (int) st.mRadius);
setCornerRadius(radius);
// TODO: Update these to be themeable.
final int topLeftRadius = a.getDimensionPixelSize(
R.styleable.DrawableCorners_topLeftRadius, radius);
final int topRightRadius = a.getDimensionPixelSize(
R.styleable.DrawableCorners_topRightRadius, radius);
final int bottomLeftRadius = a.getDimensionPixelSize(
R.styleable.DrawableCorners_bottomLeftRadius, radius);
final int bottomRightRadius = a.getDimensionPixelSize(
R.styleable.DrawableCorners_bottomRightRadius, radius);
if (topLeftRadius != radius || topRightRadius != radius ||
bottomLeftRadius != radius || bottomRightRadius != radius) {
// The corner radii are specified in clockwise order (see Path.addRoundRect())
setCornerRadii(new float[] {
topLeftRadius, topLeftRadius,
topRightRadius, topRightRadius,
bottomRightRadius, bottomRightRadius,
bottomLeftRadius, bottomLeftRadius
});
}
}通過(guò)setCornerRadius和setCornerRadii,把角度值賦值給了mGradientState屬性。
GradientDrawable進(jìn)行shape繪制
繪制自然是在draw方法內(nèi)了,大致可分為4個(gè)步驟:
@Override
public void draw(Canvas canvas) {
1、判斷是否需要繪制,如果不需要繪制,則直接return
if (!ensureValidRect()) {
// nothing to draw
return;
}
2、獲取各類變量,并依據(jù)useLayer變量設(shè)置對(duì)應(yīng)的屬性
// remember the alpha values, in case we temporarily overwrite them
// when we modulate them with mAlpha
final int prevFillAlpha = mFillPaint.getAlpha();
final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
// compute the modulate alpha values
final int currFillAlpha = modulateAlpha(prevFillAlpha);
final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha);
final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null &&
mStrokePaint.getStrokeWidth() > 0;
final boolean haveFill = currFillAlpha > 0;
final GradientState st = mGradientState;
final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mBlendModeColorFilter;
/* we need a layer iff we're drawing both a fill and stroke, and the
stroke is non-opaque, and our shapetype actually supports
fill+stroke. Otherwise we can just draw the stroke (if any) on top
of the fill (if any) without worrying about blending artifacts.
*/
final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
/* Drawing with a layer is slower than direct drawing, but it
allows us to apply paint effects like alpha and colorfilter to
the result of multiple separate draws. In our case, if the user
asks for a non-opaque alpha value (via setAlpha), and we're
stroking, then we need to apply the alpha AFTER we've drawn
both the fill and the stroke.
*/
if (useLayer) {
if (mLayerPaint == null) {
mLayerPaint = new Paint();
}
mLayerPaint.setDither(st.mDither);
mLayerPaint.setAlpha(mAlpha);
mLayerPaint.setColorFilter(colorFilter);
float rad = mStrokePaint.getStrokeWidth();
canvas.saveLayer(mRect.left - rad, mRect.top - rad,
mRect.right + rad, mRect.bottom + rad,
mLayerPaint);
// don't perform the filter in our individual paints
// since the layer will do it for us
mFillPaint.setColorFilter(null);
mStrokePaint.setColorFilter(null);
} else {
/* if we're not using a layer, apply the dither/filter to our
individual paints
*/
mFillPaint.setAlpha(currFillAlpha);
mFillPaint.setDither(st.mDither);
mFillPaint.setColorFilter(colorFilter);
if (colorFilter != null && st.mSolidColors == null) {
mFillPaint.setColor(mAlpha << 24);
}
if (haveStroke) {
mStrokePaint.setAlpha(currStrokeAlpha);
mStrokePaint.setDither(st.mDither);
mStrokePaint.setColorFilter(colorFilter);
}
}
3、根據(jù)shape四種屬性繪制對(duì)應(yīng)的圖形
switch (st.mShape) {
case RECTANGLE:
根據(jù)是否有角度,以及角度是否相同,分別采用canvas.drawRect、canvas.drawRoundRect、canvas.drawPath進(jìn)行繪制
case OVAL:
使用canvas.drawOval進(jìn)行繪制
case LINE:
使用canvas.drawLine進(jìn)行繪制
case RING:
使用canvas.drawPath進(jìn)行繪制
}
4、恢復(fù)現(xiàn)場(chǎng)
if (useLayer) {
canvas.restore();
} else {
mFillPaint.setAlpha(prevFillAlpha);
if (haveStroke) {
mStrokePaint.setAlpha(prevStrokeAlpha);
}
}
}第一部分判斷是否需要繪制全靠ensureValidRect方法,正如方法名字面意思一樣,確保有效的矩形。該方法內(nèi)部邏輯復(fù)雜,感興趣的可以自行研究,先看一下方法注釋。
/**
* This checks mGradientIsDirty, and if it is true, recomputes both our drawing
* rectangle (mRect) and the gradient itself, since it depends on our
* rectangle too.
* @return true if the resulting rectangle is not empty, false otherwise
*/
檢查變量mGradientIsDirty,如果是true,那么就重新計(jì)算mRect和gradient。返回值為mRect是否非空(也就是mRect有一個(gè)非零的大小)。
mGradientIsDirty會(huì)在一些方法中被賦值為true,例如改變了顏色、改變了gradient相關(guān)的,這意味著mRect和gradient需要重新計(jì)算。
- 第二部分依據(jù)代碼中的注釋可以非常清楚,獲取各類變量,并依據(jù)useLayer變量設(shè)置對(duì)應(yīng)的屬性。useLayer屬性,只有在設(shè)置了邊界(筆劃/stroke)和內(nèi)部填充模式,并且形狀不是線型等條件下才為true。 1.根據(jù)設(shè)置的屬性判斷是否需要再繪制一個(gè)layer; 2.如果需要layer,則創(chuàng)建layer相關(guān)屬性并根據(jù)屬性創(chuàng)建新的圖層; 3.如果不需要layer,則只設(shè)置相應(yīng)的fill/stroke屬性即可。
- 第三部分根據(jù)shape四種屬性繪制對(duì)應(yīng)的圖形。需要注意的是,這里使用的canvas.drawXXXX方法,可能是saveLayer創(chuàng)建的新圖層,也可能是沒(méi)有變過(guò)的老圖層。對(duì)于RECTANGLE,根據(jù)是否有角度,以及角度是否相同,分別采用canvas.drawRect、canvas.drawRoundRect、canvas.drawPath進(jìn)行繪制。對(duì)于OVAL,使用canvas.drawOval進(jìn)行繪制。對(duì)于LINE,使用canvas.drawLine進(jìn)行繪制。對(duì)于RING,先調(diào)用了buildRing方法返回一個(gè)Path對(duì)象,再使用canvas.drawPath進(jìn)行繪制。
- 第四部分恢復(fù)現(xiàn)場(chǎng),因?yàn)榍懊嬗衧aveLayer方法調(diào)用,那么圖層就會(huì)發(fā)生變化,如果不恢復(fù)那么后續(xù)都會(huì)在新圖層上面進(jìn)行繪制。
到此這篇關(guān)于Android shape標(biāo)簽使用方法介紹的文章就介紹到這了,更多相關(guān)Android shape標(biāo)簽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)跑馬燈效果的代碼詳解
Android中實(shí)現(xiàn)跑馬燈效果有多種方式,本文給大家介紹了Android實(shí)現(xiàn)跑馬燈效果的簡(jiǎn)單示例,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,感興趣的朋友可以參考下2018-05-05
Android自定義流式布局實(shí)現(xiàn)淘寶搜索記錄
這篇文章主要為大家詳細(xì)介紹了Android自定義流式布局實(shí)現(xiàn)淘寶搜索記錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
Android 表情面板和軟鍵盤切換時(shí)跳閃問(wèn)題的解決方法
這篇文章主要介紹了Android 表情面板和軟鍵盤切換時(shí)跳閃問(wèn)題的解決方法,需要的朋友可以參考下2017-08-08
Android中VideoView音視頻開(kāi)發(fā)的實(shí)現(xiàn)
VideoView是一個(gè)用于播放視頻的視圖組件,可以方便地在應(yīng)用程序中播放本地或網(wǎng)絡(luò)上的視頻文件,本文主要介紹了Android中VideoView音視頻開(kāi)發(fā)的實(shí)現(xiàn),具有一定的 參考價(jià)值,感興趣的可以了解一下2025-03-03
Android垃圾回收機(jī)制解決內(nèi)存泄露問(wèn)題
這篇文章主要介紹了Android垃圾回收機(jī)制解決內(nèi)存泄露問(wèn)題的相關(guān)資料,需要的朋友可以參考下2016-02-02
Android項(xiàng)目中實(shí)體類entity的作用詳解
這篇文章主要介紹了Android項(xiàng)目中實(shí)體類entity的作用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
Android自定義HorizontalScrollView打造超強(qiáng)Gallery效果
這篇文章主要介紹了Android自定義HorizontalScrollView打造圖片橫向滑動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05
Android中Serializable和Parcelable序列化對(duì)象詳解
這篇文章主要介紹了Android中Serializable和Parcelable序列化對(duì)象的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-02-02
Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化
這篇文章主要為大家介紹了Android性能優(yōu)化getResources()與Binder導(dǎo)致界面卡頓優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02

