ShapeDrawable和GradientDrawable算是drawable子類中使用頻率至關高的了,兩者的名字顯而易見,一個表示可繪製形狀,另外一個表示可繪製漸變(梯度)。
可是爲何要把這兩個看着絕不相關的可繪製類放到一塊兒講呢?
兩者類定義時的註釋中寫到這兩類均可以經過在XML中的shape標籤訂義,按照咱們正常的理解,shape標籤訂義的加載後應該是shape,gradient標籤加載後的應該是gradient。可是使用過shape標籤的同窗應該知道,shape父標籤只能定義一個shape形狀,在其內部子標籤中能夠定義gradient標籤。並且更更關鍵的是,咱們經過shape標籤訂義的對象,在代碼中加載出來以後居然是GradientDrawable!android
<shape
android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
</shape>
Drawable shape = getResources().getDrawable(R.drawable.shape);
shape.getClass().getSimpleName() // GradientDrawable
複製代碼
這也是爲何本文要把這兩個類放在一塊兒講述的緣由。程序員
和前面講到的ColorDrawable比較類似,除了特有的方法之外,大部分的方法實現基本是同樣的。canvas
若是非要找ColorDrawable和ShapeDrawable之間的差異,那麼最大的差異就在於ShapeDrawable中多了一個Shape的概念。回想一下ColorDrawable的draw方法,直接使用了canvas.drawRect方法繪製了一個矩形。而ShapeDrawable的draw方法,多了一層對Shape的處理。若是其設置了Shape形狀,那麼就會按照Shape的形狀來進行繪製,若是沒有設置Shape,那麼就會調用canvas.drawRect方法繪製矩形。數組
Shape對象一樣的,保存在了ConstantState的子類ShapeState中bash
static final class ShapeState extends ConstantState {
// shape類本身的畫筆
final @NonNull Paint mPaint;
@Config int mChangingConfigurations;
int[] mThemeAttrs;
// 保存了對應的形狀
Shape mShape;
ColorStateList mTint;
Mode mTintMode = DEFAULT_TINT_MODE;
Rect mPadding;
int mIntrinsicWidth; // 寬度
int mIntrinsicHeight; // 高度
int mAlpha = 255;// 透明度默認拉滿
ShaderFactory mShaderFactory;// 着色器
}
複製代碼
接下來看每個Drawable子類間差異最大的draw方法app
public void draw(Canvas canvas) {
final Rect r = getBounds();// 獲取矩形範圍,默認爲0
final ShapeState state = mShapeState;// 獲取關鍵的形狀狀態
final Paint paint = state.mPaint;
// 關鍵!繪製使用的是ShapeState中建立的畫筆Paint
// ColorDrawable中是用的是類中一開始就建立的畫筆Paint
final int prevAlpha = paint.getAlpha();// 獲取畫筆的透明度
paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha));
// 計算新透明度,經過setAlpha方法改變的是ShapeState中保存的alpha值
// only draw shape if it may affect output
if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) {
final boolean clearColorFilter;
if (mTintFilter != null && paint.getColorFilter() == null) {
paint.setColorFilter(mTintFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
if (state.mShape != null) {
// need the save both for the translate, and for the (unknown)
// Shape
final int count = canvas.save(); // 保存當前畫布
canvas.translate(r.left, r.top);
// 移動畫布到left,top後再繪製,至關於在新畫布的0,0位置,舊畫布的left,top處繪製
onDraw(state.mShape, canvas, paint);
// ShapeDrawable內的protected方法
canvas.restoreToCount(count);// 恢復畫布
} else {
canvas.drawRect(r, paint); // 若是不存在Shape那麼就直接繪製矩形
}
if (clearColorFilter) {
paint.setColorFilter(null);
}
}
// restore
paint.setAlpha(prevAlpha);
}
複製代碼
那麼onDraw方法又作了些什麼?動畫
protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
shape.draw(canvas, paint);// 每一個子類都會有本身的繪製流程
}
複製代碼
至此,ShapeDrawable須要瞭解的內容只有這麼多ui
前一節講述的ShapeDrawable的源代碼只有600多行,GradientDrawable的源代碼卻達到了2000+行。很明顯GradientDrawable的功能更多,至此咱們也可能稍微理解了一下爲何shape標籤加載後是GradientDrawable:shape標籤爲父標籤提供了基礎的形狀功能,gradient子標籤增長了相關的功能,因爲解析XML時還要判斷是否有gradient子標籤,因此這裏假設Google多是爲了減小複雜性,因此統一返回GradientDrawable(S有的功能G都有,G有的功能S卻沒有)
先來回顧一下gradient的功能this
<?xml version="1.0" encoding="utf-8"?>
<shape
android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:color="@color/colorAccent"/>
<solid android:color="@color/colorAccent"/>
<padding android:bottom="1dp"/>
<size android:width="100dp"/>
<corners android:radius="5dp"/>
<gradient android:startColor="@color/colorPrimary"/>
</shape>
複製代碼
咱們發現,在shape子標籤下的全部屬性在GradientDrawable類的成員變量中都能找到其影子。spa
每次看Drawable的子類時,第一個須要看的就是這個內部靜態類。它裏面所聲明的屬性確定都是這一個子類Drawable中獨有的。
final static class GradientState extends ConstantState {
// 刪除部分紅員
// 成員使用註解標記,從而存在取值範圍
public @Shape int mShape = RECTANGLE;
public @GradientType int mGradient = LINEAR_GRADIENT;
......
public @ColorInt int[] mGradientColors;
public @ColorInt int[] mTempColors; // no need to copy
public float[] mTempPositions; // no need to copy
public float[] mPositions;
public int mStrokeWidth = -1; // if >= 0 use stroking.
public float mStrokeDashWidth = 0.0f;
public float mStrokeDashGap = 0.0f;
public float mRadius = 0.0f; // use this if mRadiusArray is null
public float[] mRadiusArray = null;
......
@RadiusType int mGradientRadiusType = RADIUS_TYPE_PIXELS;
boolean mUseLevel = false;
boolean mUseLevelForShape = true;
boolean mOpaqueOverBounds;
boolean mOpaqueOverShape;
ColorStateList mTint = null;
PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
int mDensity = DisplayMetrics.DENSITY_DEFAULT;
// 數組形式存儲的各種繪製參數
int[] mThemeAttrs;
......
}
複製代碼
GradientDrawable和先前的Drawable的一個區別在於,其內部定義了許多註解,用於標記一些成員變量的取值範圍。從上面的代碼也能夠看出,對於Shape其定義了四個值:
@IntDef({RECTANGLE, OVAL, LINE, RING})
@Retention(RetentionPolicy.SOURCE)
public @interface Shape {}
複製代碼
除此以外,相比前面講述的其餘State多了一些對應的get/set方法,諸如
public void setStroke(int width, @Nullable ColorStateList colors, float dashWidth,
float dashGap) {
mStrokeWidth = width;
mStrokeColors = colors;
mStrokeDashWidth = dashWidth;
mStrokeDashGap = dashGap;
computeOpacity();
}
複製代碼
同時能夠看到,在GradientDrawable中,存在和shape的子標籤一一對應的get/set方法,由此咱們能夠知道並非Google的開發人員弄錯了(Google程序員怎麼可能會錯(手動滑稽)),而是GradientDrawable包含了幾乎所有的繪製功能,而不只僅是一個圖形(也有setShape方法)。它比ShapeDrawable更加具體,所以通用了shape父標籤。
每個Drawable子類的最大區別基本就在該方法中體現。
這個GradientDrawable的draw方法相比前面介紹的幾種更加複雜,由於有了漸變、邊線、圓角等額外處理。
這裏能夠簡單分爲四個階段進行繪製 首先看一下draw方法的大概流程
public void draw(Canvas canvas) {
// 1.判斷是否須要繪製,若是不須要繪製,則直接return
if (!ensureValidRect()) {
// nothing to draw
return;
}
// 2.獲取各種變量,並依據useLayer變量設置對應的屬性
final int prevFillAlpha = mFillPaint.getAlpha();
final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
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 : mTintFilter;
final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
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.根據shape屬性繪製對應的圖形
switch (st.mShape) {
case RECTANGLE:
// 省略
break;
case OVAL:
// 省略
break;
case LINE:
// 省略
break;
case RING:
// 省略
break;
}
// 4.恢復現場
if (useLayer) {
canvas.restore();
} else {
mFillPaint.setAlpha(prevFillAlpha);
if (haveStroke) {
mStrokePaint.setAlpha(prevStrokeAlpha);
}
}
}
複製代碼
先看一段代碼註釋
/**
* 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,那麼就從新計算mRect和gradient
返回值:mRect(GradientDrawable最開始會初始化new Rect())是否爲空,返回!mRect.isEmpty()
*/
mRect.isEmpty()方法返回的是left >= right || top >= bottom
因此說,若是咱們沒有給mRect賦值一個非0的大小,那麼isEmpty就是true
看名字也知道是「確保可用的矩形」,即最終返回true表示是valid
複製代碼
從代碼註釋中能夠看到,該方法和mGradientIsDirty相關,那麼這個變量什麼時候有改變呢?
在以上的幾個方法中,mGradientIsDirty會被設置爲true,這就對應着註釋中要從新計算的那一部分private boolean ensureValidRect() {
// 能夠看到只有一個大的if,若是值爲false,則直接走到最後一句return
// 這個大if代碼塊裏,都是對漸變塊的處理,利用mRect的範圍更新內部漸變色塊
if (mGradientIsDirty) {
mGradientIsDirty = false;
Rect bounds = getBounds();
float inset = 0;
if (mStrokePaint != null) {
inset = mStrokePaint.getStrokeWidth() * 0.5f;
}
final GradientState st = mGradientState;
mRect.set(bounds.left + inset, bounds.top + inset,
bounds.right - inset, bounds.bottom - inset);
final int[] gradientColors = st.mGradientColors;
if (gradientColors != null) {
final RectF r = mRect;
final float x0, x1, y0, y1;
if (st.mGradient == LINEAR_GRADIENT) {
// 省略對應的邏輯
} else if (st.mGradient == RADIAL_GRADIENT) {
// 省略對應的邏輯
} else if (st.mGradient == SWEEP_GRADIENT) {
// 省略對應的邏輯
}
// 若是沒有設置顏色,則填充顏色默認按照黑色
if (st.mSolidColors == null) {
mFillPaint.setColor(Color.BLACK);
}
}
}
// 能夠看到,並無對mRect進行處理,只是更新其內部gradient的狀態,這也和變量名相呼應
// 因此,若是一開始就沒有設置大小,那麼這裏返回確定是false
return !mRect.isEmpty();
}
複製代碼
這裏,draw方法的第一部分就結束了,總結一下就是: 1.先判斷是否更新了內部漸變色塊的屬性,若是沒有變動,直接返回區域是否不爲空 2.若是有變動,則先按照區域大小和邊界大小對漸變色塊進行更新,而後返回區域是否不爲空 3.若是區域mRect爲空,那麼draw方法就會直接return,避免了無用繪製
final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
// useLayer屬性,只有在設置了邊界(筆劃/stroke)和內部填充模式,而且形狀不是線型等條件下才爲true
if (useLayer) {
if (mLayerPaint == null) {
mLayerPaint = new Paint();
}
mLayerPaint.setDither(st.mDither);
mLayerPaint.setAlpha(mAlpha);
mLayerPaint.setColorFilter(colorFilter);
float rad = mStrokePaint.getStrokeWidth();
// mRect+stroke區域被保存下來,由於是一個層級
// X軸向右正向,Y軸向下正向
// 同時,建立了一個新的繪製圖層
canvas.saveLayer(mRect.left - rad, mRect.top - rad,
mRect.right + rad, mRect.bottom + rad,
mLayerPaint);
// mFillPaint和mStrokePaint都是開發者可控的
mFillPaint.setColorFilter(null);
mStrokePaint.setColorFilter(null);
} else {
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);
}
}
}
複製代碼
至此,第二部分也結束了。總結一下就是: 1.其根據設置的屬性判斷是否須要再繪製一個layer 2.若是須要layer,則建立layer相關屬性並根據屬性建立新的圖層 3.若是不須要layer,則只設置相應的fill/stroke屬性便可
這裏使用switch---case對四種不一樣的形狀進行繪製。
須要注意的是,這是時候使用的canvas.drawXXXX方法,多是saveLayer建立的新圖層,也多是沒有變過的老圖層。
case RECTANGLE:
if (st.mRadiusArray != null) {
buildPathIfDirty();// 該方法用於建立非四個相同圓角矩形路徑path,源碼是使用path的形式進行繪製的哦!
canvas.drawPath(mPath, mFillPaint);
if (haveStroke) {
canvas.drawPath(mPath, mStrokePaint);
}
複製代碼
由於前面有saveLayer方法調用,那麼圖層就會發生變化,若是不恢復那麼後續都會在新圖層上面進行繪製。
if (useLayer) {
canvas.restore();// 恢復圖層
} else {
mFillPaint.setAlpha(prevFillAlpha);// 恢復透明度
if (haveStroke) {
mStrokePaint.setAlpha(prevStrokeAlpha);
}
}
複製代碼
咱們不從ResourceImpl分析,只看GradientDrawable的源碼,由於從xml中定義,必然會有一個xml的解析過程,那麼就找一下。
還真找到了。。。
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
TypedArray a;
int type;
final int innerDepth = parser.getDepth() + 1;
int depth;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth=parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth) {
continue;
}
String name = parser.getName();
// 能夠看到這些都是shape父標籤下的子標籤對應的字段
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);
}
}
}
複製代碼
那究竟是在哪裏解析了呢?平時咱們解析layout文件的時候常常會使用LayoutInflater,那麼必然的,Drawable也存在對應的DrawableInflater
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;
}
}
複製代碼
至此,咱們也知道了shape標籤下的xml最終會被解析爲GradientDrawable。
下一篇將講講剩下簡單一點的諸如LevelList/Picture/StateList等