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相关,那么这个变量什么时候有改变呢?
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等