Drawable源码解读

前言

Android里最常用的5大包名

package:

android.animation

android.text

android.view

android.widget

android.graphics

本篇介绍android.graphics当中的drawable

Though usually not visible to the application, Drawables may take a variety

of forms:

  • Bitmap: the simplest Drawable, a PNG or JPEG image.
  • Nine Patch: an extension to the PNG format allows it to

    specify information about how to stretch it and place things inside of

    it.

  • Shape: contains simple drawing commands instead of a raw

    bitmap, allowing it to resize better in some cases.

  • Layers: a compound drawable, which draws multiple underlying

    drawables on top of each other.

  • States: a compound drawable that selects one of a set of

    drawables based on its state.

  • Levels: a compound drawable that selects one of a set of

    drawables based on its level.

  • Scale: a compound drawable with a single child drawable,

    whose overall size is modified based on the current level.

Drawable方法论

分类

Drawable_Method

Drawable的子类们

大纲

Drawable_OverView

ShapeDrawable

draw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Override
public void draw(Canvas canvas) {
final Rect r = getBounds();//get how large with location
final ShapeState state = mShapeState;
final Paint paint = state.mPaint;

final int prevAlpha = paint.getAlpha();
paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha));

// 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);
onDraw(state.mShape, canvas, paint);// see below
canvas.restoreToCount(count);
} else {
canvas.drawRect(r, paint);
}

if (clearColorFilter) {
paint.setColorFilter(null);
}
}

// restore
paint.setAlpha(prevAlpha);
}
onDraw
1
2
3
4
5
6
7
8
/**
* Called from the drawable's draw() method after the canvas has been set to
* draw the shape at (0,0). Subclasses can override for special effects such
* as multiple layers, stroking, etc.
*/
protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
shape.draw(canvas, paint);
}
shape与shapeDrawable

Drawable_Shape

ShapeState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* Defines the intrinsic properties of this ShapeDrawable's Shape.
*/
final static class ShapeState extends ConstantState {
int[] mThemeAttrs;
int mChangingConfigurations;
Paint mPaint;
Shape mShape;
ColorStateList mTint = null;
Mode mTintMode = DEFAULT_TINT_MODE;
Rect mPadding;
int mIntrinsicWidth;
int mIntrinsicHeight;
int mAlpha = 255;
ShaderFactory mShaderFactory;

ShapeState(ShapeState orig) {
if (orig != null) {
mThemeAttrs = orig.mThemeAttrs;
mPaint = orig.mPaint;
mShape = orig.mShape;
mTint = orig.mTint;
mTintMode = orig.mTintMode;
mPadding = orig.mPadding;
mIntrinsicWidth = orig.mIntrinsicWidth;
mIntrinsicHeight = orig.mIntrinsicHeight;
mAlpha = orig.mAlpha;
mShaderFactory = orig.mShaderFactory;
} else {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
}

@Override
public boolean canApplyTheme() {
return mThemeAttrs != null
|| (mTint != null && mTint.canApplyTheme());
}

@Override
public Drawable newDrawable() {
return new ShapeDrawable(this, null);
}

@Override
public Drawable newDrawable(Resources res) {
return new ShapeDrawable(this, res);
}

@Override
public int getChangingConfigurations() {
return mChangingConfigurations
| (mTint != null ? mTint.getChangingConfigurations() : 0);
}
}

ColorDrawable

draw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void draw(Canvas canvas) {
final ColorFilter colorFilter = mPaint.getColorFilter();
if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
if (colorFilter == null) {
mPaint.setColorFilter(mTintFilter);
}

mPaint.setColor(mColorState.mUseColor);
canvas.drawRect(getBounds(), mPaint);

// Restore original color filter.
mPaint.setColorFilter(colorFilter);
}
}

BitmapDrawable

draw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    @Override
public void draw(Canvas canvas) {
final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap == null) {
return;
}

final BitmapState state = mBitmapState;
// config with shader
final Paint paint = state.mPaint;
if (state.mRebuildShader) {
final Shader.TileMode tmx = state.mTileModeX;
final Shader.TileMode tmy = state.mTileModeY;
if (tmx == null && tmy == null) {
paint.setShader(null);
} else {
paint.setShader(new BitmapShader(bitmap,
tmx == null ? Shader.TileMode.CLAMP : tmx,
tmy == null ? Shader.TileMode.CLAMP : tmy));
}

state.mRebuildShader = false;
}

//config with alpha
final int restoreAlpha;
if (state.mBaseAlpha != 1.0f) {
final Paint p = getPaint();
restoreAlpha = p.getAlpha();
p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
} else {
restoreAlpha = -1;
}

//config with colorFilter
final boolean clearColorFilter;
if (mTintFilter != null && paint.getColorFilter() == null) {
paint.setColorFilter(mTintFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}

updateDstRectAndInsetsIfDirty();
final Shader shader = paint.getShader();
final boolean needMirroring = needMirroring();
if (shader == null) {
// deal with mirroring
if (needMirroring) {
canvas.save();
// Mirror the bitmap
canvas.translate(mDstRect.right - mDstRect.left, 0);
canvas.scale(-1.0f, 1.0f);
}
// draw bitmap
canvas.drawBitmap(bitmap, null, mDstRect, paint);

if (needMirroring) {
canvas.restore();
}
} else {
if (needMirroring) {
// Mirror the bitmap
updateMirrorMatrix(mDstRect.right - mDstRect.left);
shader.setLocalMatrix(mMirrorMatrix);
paint.setShader(shader);
} else {
if (mMirrorMatrix != null) {
mMirrorMatrix = null;
shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
paint.setShader(shader);
}
}

canvas.drawRect(mDstRect, paint);
}

//resotre
if (clearColorFilter) {
paint.setColorFilter(null);
}

if (restoreAlpha >= 0) {
paint.setAlpha(restoreAlpha);
}
}

LayerDrawable

draw
1
2
3
4
5
6
7
8
9
10
11
@Override
public void draw(Canvas canvas) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
final Drawable dr = array[i].mDrawable;
if (dr != null) {
dr.draw(canvas);
}
}
}
LayerState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  static class LayerState extends ConstantState {
int mNum;
ChildDrawable[] mChildren;
int[] mThemeAttrs;

int mPaddingTop = -1;
int mPaddingBottom = -1;
int mPaddingLeft = -1;
int mPaddingRight = -1;
int mPaddingStart = -1;
int mPaddingEnd = -1;
int mOpacityOverride = PixelFormat.UNKNOWN;

int mChangingConfigurations;
int mChildrenChangingConfigurations;

private boolean mHaveOpacity;
private int mOpacity;

private boolean mHaveIsStateful;
private boolean mIsStateful;

private boolean mAutoMirrored = false;

private int mPaddingMode = PADDING_MODE_NEST;
'
LayerState(LayerState orig, LayerDrawable owner, Resources res) {
if (orig != null) {
final ChildDrawable[] origChildDrawable = orig.mChildren;
final int N = orig.mNum;

mNum = N;
mChildren = new ChildDrawable[N];

mChangingConfigurations = orig.mChangingConfigurations;
mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;

for (int i = 0; i < N; i++) {
final ChildDrawable or = origChildDrawable[i];
mChildren[i] = new ChildDrawable(or, owner, res);
}

mHaveOpacity = orig.mHaveOpacity;
mOpacity = orig.mOpacity;
mHaveIsStateful = orig.mHaveIsStateful;
mIsStateful = orig.mIsStateful;
mAutoMirrored = orig.mAutoMirrored;
mPaddingMode = orig.mPaddingMode;
mThemeAttrs = orig.mThemeAttrs;
mPaddingTop = orig.mPaddingTop;
mPaddingBottom = orig.mPaddingBottom;
mPaddingLeft = orig.mPaddingLeft;
mPaddingRight = orig.mPaddingRight;
mPaddingStart = orig.mPaddingStart;
mPaddingEnd = orig.mPaddingEnd;
mOpacityOverride = orig.mOpacityOverride;
} else {
mNum = 0;
mChildren = null;
}
}
''''''''''''''''''''''''''''''''''''''''''''''''''''
}

VertorDrawable

如何建立矢量树
相关类

Drawable_Struct

建树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
eg:
* <vector xmlns:android="http://schemas.android.com/apk/res/android"
* android:height="64dp"
* android:width="64dp"
* android:viewportHeight="600"
* android:viewportWidth="600" >
* <group
* android:name="rotationGroup"
* android:pivotX="300.0"
* android:pivotY="300.0"
* android:rotation="45.0" >
* <path
* android:name="v"
* android:fillColor="#000000"
* android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
* </group>
* </vector>



private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final VectorDrawableState state = mVectorState;
final VPathRenderer pathRenderer = state.mVPathRenderer;
boolean noPathTag = true;

// important Use a stack to help to build the group tree.
// The top of the stack is always the current group.
final Stack<VGroup> groupStack = new Stack<VGroup>();
//use mRootGroup to store the sequense of shi children and grandchild
groupStack.push(pathRenderer.mRootGroup);

int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
//get nearest parent
final VGroup currentGroup = groupStack.peek();
//"path"
if (SHAPE_PATH.equals(tagName)) {
final VFullPath path = new VFullPath();
path.inflate(res, attrs, theme);
currentGroup.mChildren.add(path);
if (path.getPathName() != null) {
// store the detail infomation of path data for via inflate
pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
}
noPathTag = false;
state.mChangingConfigurations |= path.mChangingConfigurations;
}
//"clip-path"
else if (SHAPE_CLIP_PATH.equals(tagName)) {
final VClipPath path = new VClipPath();
path.inflate(res, attrs, theme);
currentGroup.mChildren.add(path);
if (path.getPathName() != null) {
pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
}
state.mChangingConfigurations |= path.mChangingConfigurations;
}
//"group"
else if (SHAPE_GROUP.equals(tagName)) {
VGroup newChildGroup = new VGroup();
newChildGroup.inflate(res, attrs, theme);
currentGroup.mChildren.add(newChildGroup);
//update nearest tree parent
groupStack.push(newChildGroup);
if (newChildGroup.getGroupName() != null) {
pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
newChildGroup);
}
state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
}
} else if (eventType == XmlPullParser.END_TAG) {
final String tagName = parser.getName();
if (SHAPE_GROUP.equals(tagName)) {
// tree parent added all its children and remove from the stack
groupStack.pop();
}
}
eventType = parser.next();
}

// Print the tree out for debug.
if (DBG_VECTOR_DRAWABLE) {
printGroupTree(pathRenderer.mRootGroup, 0);
}

if (noPathTag) {
final StringBuffer tag = new StringBuffer();

if (tag.length() > 0) {
tag.append(" or ");
}
tag.append(SHAPE_PATH);

throw new XmlPullParserException("no " + tag + " defined");
}
}
如何画

VectorDrawable:draw(Canvas ) ->VectorDrawable:updateCachedBitmap(int width, int height)

->VPathRenderer:draw(Canvas canvas, int w, int h, ColorFilter filter)

->VPathRenderer:drawGroupTree(VGroup , Matrix, Canvas, int w, int h, ColorFilter)

->VPathRenderer:drawPath(VGroup, VPath, Canvas, int w, int h, ColorFilter)

step 1: 确定canvas 的宽高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Override
public void draw(Canvas canvas) {
// We will offset the bounds for drawBitmap, so copyBounds() here instead
// of getBounds().
copyBounds(mTmpBounds);
if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
// Nothing to draw
return;
}

// Color filters always override tint filters.
final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);

// The imageView can scale the canvas in different ways, in order to
// avoid blurry scaling, we have to draw into a bitmap with exact pixel
// size first. This bitmap size is determined by the bounds and the
// canvas scale.
//特殊处理imageview的显示情况
canvas.getMatrix(mTmpMatrix);
mTmpMatrix.getValues(mTmpFloats);
float canvasScaleX = Math.abs(mTmpFloats[Matrix.MSCALE_X]);
float canvasScaleY = Math.abs(mTmpFloats[Matrix.MSCALE_Y]);
int scaledWidth = (int) (mTmpBounds.width() * canvasScaleX);
int scaledHeight = (int) (mTmpBounds.height() * canvasScaleY);
scaledWidth = Math.min(MAX_CACHED_BITMAP_SIZE, scaledWidth);
scaledHeight = Math.min(MAX_CACHED_BITMAP_SIZE, scaledHeight);

if (scaledWidth <= 0 || scaledHeight <= 0) {
return;
}

final int saveCount = canvas.save();
canvas.translate(mTmpBounds.left, mTmpBounds.top);

// Handle RTL mirroring.
final boolean needMirroring = needMirroring();
if (needMirroring) {
canvas.translate(mTmpBounds.width(), 0);
canvas.scale(-1.0f, 1.0f);
}

// At this point, canvas has been translated to the right position.
// And we use this bound for the destination rect for the drawBitmap, so
// we offset to (0, 0);
mTmpBounds.offsetTo(0, 0);

//计算获得显示的scaleWidth和scaleHeight
mVectorState.createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
if (!mAllowCaching) {
mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
} else {
if (!mVectorState.canReuseCache()) {
mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
mVectorState.updateCacheStates();
}
}
mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter, mTmpBounds);
canvas.restoreToCount(saveCount);
}
step 2:调用PathRenderer
1
2
3
4
5
6
7
public void updateCachedBitmap(int width, int height) {
mCachedBitmap.eraseColor(Color.TRANSPARENT);
//其他的canvas是哪里来的?
Canvas tmpCanvas = new Canvas(mCachedBitmap);
//画出drawable
mVPathRenderer.draw(tmpCanvas, width, height, null);
}
step 3:递归画路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
     public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
// Travese the tree in pre-order to draw.
drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvas, w, h, filter);
}



private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
Canvas canvas, int w, int h, ColorFilter filter) {
// Calculate current group's matrix by preConcat the parent's and
// and the current one on the top of the stack.
// Basically the Mfinal = Mviewport * M0 * M1 * M2;
// Mi the local matrix at level i of the group tree.
currentGroup.mStackedMatrix.set(currentMatrix);
currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
//mStackedMatrix本身pre,mLocaMatrix即group的配置转换成matrix

// Save the current clip information, which is local to this group.
canvas.save();
// Draw the group tree in the same order as the XML file.
for (int i = 0; i < currentGroup.mChildren.size(); i++) {
Object child = currentGroup.mChildren.get(i);
if (child instanceof VGroup) {
VGroup childGroup = (VGroup) child;
drawGroupTree(childGroup, currentGroup.mStackedMatrix,
canvas, w, h, filter);
} else if (child instanceof VPath) {
VPath childPath = (VPath) child;
drawPath(currentGroup, childPath, canvas, w, h, filter);
}
}
canvas.restore();
}


private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
ColorFilter filter) {
final float scaleX = w / mViewportWidth;
final float scaleY = h / mViewportHeight;
final float minScale = Math.min(scaleX, scaleY);
final Matrix groupStackedMatrix = vGroup.mStackedMatrix;

mFinalPathMatrix.set(groupStackedMatrix);
mFinalPathMatrix.postScale(scaleX, scaleY);

final float matrixScale = getMatrixScale(groupStackedMatrix);
if (matrixScale == 0) {
// When either x or y is scaled to 0, we don't need to draw anything.
return;
}
//important: pathdata 转 path ,use PathParser to transform the node data to path
vPath.toPath(mPath);
final Path path = mPath;

mRenderPath.reset();

if (vPath.isClipPath()) {
mRenderPath.addPath(path, mFinalPathMatrix);
canvas.clipPath(mRenderPath);
} else {
VFullPath fullPath = (VFullPath) vPath;
if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;

if (mPathMeasure == null) {
mPathMeasure = new PathMeasure();
}
mPathMeasure.setPath(mPath, false);

float len = mPathMeasure.getLength();
start = start * len;
end = end * len;
path.reset();
if (start > end) {
mPathMeasure.getSegment(start, len, path, true);
//用pathMeasure截取path中的一段进行展示,moveto Start position
mPathMeasure.getSegment(0f, end, path, true);
} else {
mPathMeasure.getSegment(start, end, path, true);
}
path.rLineTo(0, 0); // fix bug in measure
}
mRenderPath.addPath(path, mFinalPathMatrix);

if (fullPath.mFillColor != Color.TRANSPARENT) {
if (mFillPaint == null) {
mFillPaint = new Paint();
mFillPaint.setStyle(Paint.Style.FILL);
mFillPaint.setAntiAlias(true);
}

final Paint fillPaint = mFillPaint;
fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
fillPaint.setColorFilter(filter);
canvas.drawPath(mRenderPath, fillPaint);
}

if (fullPath.mStrokeColor != Color.TRANSPARENT) {
if (mStrokePaint == null) {
mStrokePaint = new Paint();
mStrokePaint.setStyle(Paint.Style.STROKE);
mStrokePaint.setAntiAlias(true);
}

final Paint strokePaint = mStrokePaint;
if (fullPath.mStrokeLineJoin != null) {
strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
}

if (fullPath.mStrokeLineCap != null) {
strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
}

strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
strokePaint.setColorFilter(filter);
final float finalStrokeScale = minScale * matrixScale;
strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
canvas.drawPath(mRenderPath, strokePaint);
}
}
}

GradientDrawable

Create
  1. create GradientDrawable

  2. children’s element

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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);
    }
Draw

各种Canvas的基本用法组合[*用以学习Canvas的使用场景]

DrawableContainer

三个方法四个参数
Method 1:selectDrawable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public boolean selectDrawable(int idx) {
if (idx == mCurIndex) {
return false;
}

final long now = SystemClock.uptimeMillis();

if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx
+ ": exit=" + mDrawableContainerState.mExitFadeDuration
+ " enter=" + mDrawableContainerState.mEnterFadeDuration);

//mDrawableContainerState.mExitFadeDuration get by stateListDrawable enterFadeDuration
if (mDrawableContainerState.mExitFadeDuration > 0) {
if (mLastDrawable != null) {
mLastDrawable.setVisible(false, false);
}
if (mCurrDrawable != null) {
// swtich drawable and calculate last exit end time
mLastDrawable = mCurrDrawable;
mLastIndex = mCurIndex;
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}

if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(idx);
//new current state and calculate last enter time
mCurrDrawable = d;
mCurIndex = idx;
if (d != null) {
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
}
//Initializes a drawable for display in this container
initializeDrawableForDisplay(d);
}
} else {
mCurrDrawable = null;
mCurIndex = -1;
}

if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
//do animating
animate(true);
invalidateSelf();
}
};
} else {
unscheduleSelf(mAnimationRunnable);
}
// Compute first frame and schedule next animation.
animate(true);
}

invalidateSelf();

return true;
}
Method 2 animate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 void animate(boolean schedule) {
mHasAlpha = true;

final long now = SystemClock.uptimeMillis();
boolean animating = false;
if (mCurrDrawable != null) {
if (mEnterAnimationEnd != 0) {
if (mEnterAnimationEnd <= now) {
mCurrDrawable.mutate().setAlpha(mAlpha);
mEnterAnimationEnd = 0;
} else {
int animAlpha = (int)((mEnterAnimationEnd-now)*255)
/ mDrawableContainerState.mEnterFadeDuration;
if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
// be clearer with percent enter
mCurrDrawable.mutate().setAlpha(((255-animAlpha)*mAlpha)/255);
animating = true;
}
}
} else {
mEnterAnimationEnd = 0;
}
if (mLastDrawable != null) {
if (mExitAnimationEnd != 0) {
if (mExitAnimationEnd <= now) {
mLastDrawable.setVisible(false, false);
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
} else {
int animAlpha = (int)((mExitAnimationEnd-now)*255)
/ mDrawableContainerState.mExitFadeDuration;
if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
// be more transparent with percent exit
mLastDrawable.mutate().setAlpha((animAlpha*mAlpha)/255);
animating = true;
}
}
} else {
mExitAnimationEnd = 0;
}

if (schedule && animating) {
scheduleSelf(mAnimationRunnable, now + 1000 / 60);
}
}
Method 3 initializeDrawableForDisplay:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Initializes a drawable for display in this container.
*
* @param d The drawable to initialize.
*/
private void initializeDrawableForDisplay(Drawable d) {
d.mutate();

if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
d.setAlpha(mAlpha);
}

if (mDrawableContainerState.mHasColorFilter) {
// Color filter always overrides tint.
d.setColorFilter(mDrawableContainerState.mColorFilter);
} else {
if (mDrawableContainerState.mHasTintList) {
d.setTintList(mDrawableContainerState.mTintList);
}
if (mDrawableContainerState.mHasTintMode) {
d.setTintMode(mDrawableContainerState.mTintMode);
}
}

d.setVisible(isVisible(), true);
d.setDither(mDrawableContainerState.mDither);
d.setState(getState());
d.setLevel(getLevel());
d.setBounds(getBounds());
d.setLayoutDirection(getLayoutDirection());
d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);

final Rect hotspotBounds = mHotspotBounds;
if (hotspotBounds != null) {
d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
hotspotBounds.right, hotspotBounds.bottom);
}
}
Parameters:
1
2
3
4
private Drawable mCurrDrawable;//管理前后状态
private Drawable mLastDrawable;//管理前后状态
private int mCurIndex = -1;//管理前后状态
Drawable[] mDrawables;//所有的drawable数组
子类们
AnimatableDrawable
  1. xmlTag

    Drawable:createFromXmlInner(Resources , XmlPullParser, AttributeSet, Theme)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    * <pre>
    * <!-- Animation frames are wheel0.png through wheel5.png
    * files inside the res/drawable/ folder -->
    * <animation-list android:id="@+id/selected" android:oneshot="false">
    * <item android:drawable="@drawable/wheel0" android:duration="50" />
    * <item android:drawable="@drawable/wheel1" android:duration="50" />
    * <item android:drawable="@drawable/wheel2" android:duration="50" />
    * <item android:drawable="@drawable/wheel3" android:duration="50" />
    * <item android:drawable="@drawable/wheel4" android:duration="50" />
    * <item android:drawable="@drawable/wheel5" android:duration="50" />
    * </animation-list></pre>
    * <p>
  2. 切换流程

    1
    start -> run -> nextFrame -> setFrame -> scheduleSelf(Runnable,when)
    scheduleDrawable的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 1.通过 View:setBackgroudDrawable 中的  background.setCallback(this);
    // 2.成功 回调View:scheduleDrawable
    /**
    * Schedules an action on a drawable to occur at a specified time.
    *
    * @param who the recipient of the action
    * @param what the action to run on the drawable
    * @param when the time at which the action must occur. Uses the
    * {@link SystemClock#uptimeMillis} timebase.
    */
    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
    if (verifyDrawable(who) && what != null) {
    //updatimeMillis开机后的经过的时间,不包括深度睡眠的时间,delay 约为duration
    final long delay = when - SystemClock.uptimeMillis();
    if (mAttachInfo != null) {
    mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
    Choreographer.CALLBACK_ANIMATION, what, who,
    Choreographer.subtractFrameDelay(delay));
    } else {
    //下一个frame,what = animationDrawable run nextFrame
    ViewRootImpl.getRunQueue().postDelayed(what, delay);
    }
    }
    }

  3. mRunnig vs Animating

    1
    2
    3
    4
    5
    /** Whether the drawable has an animation callback posted. *///是否有callback被posted
    private boolean mRunning;

    /** Whether the drawable should animate when visible. *///可视状态是否需要继续执行动画
    private boolean mAnimating;

LevelListDrawable
  1. xmlTag

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * A resource that manages a number of alternate Drawables, each assigned a maximum numerical value.
    * Setting the level value of the object with {@link #setLevel(int)} will load the image with the next
    * greater or equal value assigned to its max attribute.
    * A good example use of
    * a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current
    * battery level.
    * <p>
    * It can be defined in an XML file with the <code><level-list></code> element.
    * Each Drawable level is defined in a nested <code><item></code>. For example:
    * </p>
    * <pre>
    * <level-list xmlns:android="http://schemas.android.com/apk/res/android">
    * <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
    * <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
    * <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
    * <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
    * </level-list>
    *</pre>
    * <p>With this XML saved into the res/drawable/ folder of the project, it can be referenced as
    * the drawable for an {@link android.widget.ImageView}. The default image is the first in the list.
    * It can then be changed to one of the other levels with
    * {@link android.widget.ImageView#setImageLevel(int)}. For more
    * information, see the guide to <a
    * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
    *
    * @attr ref android.R.styleable#LevelListDrawableItem_minLevel
    * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel
    * @attr ref android.R.styleable#LevelListDrawableItem_drawable
    */
  2. onLevelChange

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //private int[] mLows;  //use to stroe drawable level zone by pos
    //private int[] mHighs; //use to store drawable level zone by pos

    /**
    invoke by setLevel on any other method and then find the most match child to display
    by the mLows and mHighs
    **/
    @Override
    protected boolean onLevelChange(int level) {
    int idx = mLevelListState.indexOfLevel(level);
    if (selectDrawable(idx)) {
    return true;
    }
    return super.onLevelChange(level);
    }
StateListDrawable
  1. how to inflater

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    /**
    * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
    * ID value.
    * <p/>
    * <p>It can be defined in an XML file with the <code><selector></code> element.
    * Each state Drawable is defined in a nested <code><item></code> element. For more
    * information, see the guide to <a
    * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
    *
    * @attr ref android.R.styleable#StateListDrawable_visible
    * @attr ref android.R.styleable#StateListDrawable_variablePadding
    * @attr ref android.R.styleable#StateListDrawable_constantSize
    * @attr ref android.R.styleable#DrawableStates_state_focused
    * @attr ref android.R.styleable#DrawableStates_state_window_focused
    * @attr ref android.R.styleable#DrawableStates_state_enabled
    * @attr ref android.R.styleable#DrawableStates_state_checkable
    * @attr ref android.R.styleable#DrawableStates_state_checked
    * @attr ref android.R.styleable#DrawableStates_state_selected
    * @attr ref android.R.styleable#DrawableStates_state_activated
    * @attr ref android.R.styleable#DrawableStates_state_active
    * @attr ref android.R.styleable#DrawableStates_state_single
    * @attr ref android.R.styleable#DrawableStates_state_first
    * @attr ref android.R.styleable#DrawableStates_state_middle
    * @attr ref android.R.styleable#DrawableStates_state_last
    * @attr ref android.R.styleable#DrawableStates_state_pressed
    */
    // use StateSet to initial state array


    // store in int[][] mStateSets;

    /**
    * Extracts state_ attributes from an attribute set.
    *
    * @param attrs The attribute set.
    * @return An array of state_ attributes.
    */
    int[] extractStateSet(AttributeSet attrs) {
    int j = 0;
    final int numAttrs = attrs.getAttributeCount();
    int[] states = new int[numAttrs];
    for (int i = 0; i < numAttrs; i++) {
    final int stateResId = attrs.getAttributeNameResource(i);
    switch (stateResId) {
    case 0:
    break;
    case R.attr.drawable:
    case R.attr.id:
    // Ignore attributes from StateListDrawableItem and
    // AnimatedStateListDrawableItem.
    continue;
    default:
    states[j++] = attrs.getAttributeBooleanValue(i, false)
    ? stateResId : -stateResId;
    }
    }
    states = StateSet.trimStateSet(states, j);
    return states;
    }
    1
    2
    3
    4
    5
    6
    7



    2. onStateChange

    ```java
    View:onTouchEvent()->setPressed()->refreshDrawableState()->drawableStateChange()->setState()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
	//View
protected void drawableStateChanged() {
//here to generate the current Drawable State
final int[] state = getDrawableState();

final Drawable bg = mBackground;
if (bg != null && bg.isStateful()) {
bg.setState(state);
}

final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (fg != null && fg.isStateful()) {
fg.setState(state);
}

if (mScrollCache != null) {
final Drawable scrollBar = mScrollCache.scrollBar;
if (scrollBar != null && scrollBar.isStateful()) {
scrollBar.setState(state);
}
}

if (mStateListAnimator != null) {
mStateListAnimator.setState(state);
}
}


/**
* Return an array of resource IDs of the drawable states representing the
* current state of the view.
*
* @return The current drawable state
*
* @see Drawable#setState(int[])
* @see #drawableStateChanged()
* @see #onCreateDrawableState(int)
*/
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
//no change
return mDrawableState;
} else {
// genearate new current Drawablestate
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}


/**
* Generate the new {@link android.graphics.drawable.Drawable} state for
* this view. This is called by the view
* system when the cached Drawable state is determined to be invalid. To
* retrieve the current state, you should use {@link #getDrawableState}.
*
* @param extraSpace if non-zero, this is the number of extra entries you
* would like in the returned array in which you can place your own
* states.
*
* @return Returns an array holding the current {@link Drawable} state of
* the view.
*
* @see #mergeDrawableStates(int[], int[])
*/
protected int[] onCreateDrawableState(int extraSpace) {
if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
mParent instanceof View) {
return ((View) mParent).onCreateDrawableState(extraSpace);
}

int[] drawableState;

int privateFlags = mPrivateFlags;

int viewStateIndex = 0;
if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
// process doesn't allow it. This is just to allow app preview
// windows to better match their app.
viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
}
if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;

final int privateFlags2 = mPrivateFlags2;
if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
}
if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
}

drawableState = StateSet.get(viewStateIndex);

//noinspection ConstantIfStatement
if (false) {
Log.i("View", "drawableStateIndex=" + viewStateIndex);
Log.i("View", toString()
+ " pressed=" + ((privateFlags & PFLAG_PRESSED) != 0)
+ " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+ " fo=" + hasFocus()
+ " sl=" + ((privateFlags & PFLAG_SELECTED) != 0)
+ " wf=" + hasWindowFocus()
+ ": " + Arrays.toString(drawableState));
}

if (extraSpace == 0) {
return drawableState;
}

final int[] fullState;
if (drawableState != null) {
fullState = new int[drawableState.length + extraSpace];
System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
} else {
fullState = new int[extraSpace];
}

return fullState;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//invoke StateListDrawable:onStateChange at last

@Override
protected boolean onStateChange(int[] stateSet) {
final boolean changed = super.onStateChange(stateSet);

int idx = mStateListState.indexOfStateSet(stateSet);
if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
+ Arrays.toString(stateSet) + " found " + idx);
if (idx < 0) {
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}

return selectDrawable(idx) || changed;
}
总结
展示

DrawableContainer 子类分2类:一类主动刷新的,如AnimationDrawable,通过scheduleDrawable的方式控制每个drawable的展示时间,由View的scheduleDrawable控制流程,由自身的nextFrame控制展示的内容;另一类是被动刷新的,如stateListDrawable,由View保存并控制状态变脸,由自身在onStateChange中找出之前inflater保存的 mStateSets[][]最匹配drawable

生成
  1. 通过updateStateFromTypeArray获得外层属性
  2. 通过inflateChildrenElement填充Drawable数组
  3. 自身的state内部类保存选择drawable条件的相关数据,DrawableContainer的state保存所有drawable数组以及显示的bounds,level等参数