背景

本篇是对上一篇 UETool 的扩展,主要是在其基础上添加对 GradientDrawable 的解析。先看看当前 UEToolbackground 属性的解析:

这里的解析是不完善的,GradientDrawable 对应的是一个 Xml 资源文件,真正的解析应该是给出这个 Xml 文件的具体内容。这就引出了本文的要解决的问题,如何解析 GradientDrawable

1.关于 Drawable 及其子类的知识参考官方文档:可绘制对象资源
2.关于 ShapeDrawable 和 GradientDrawable 的疑问可以参考:Android的ShapeDrawable和GradientDrawable源码解析
3.Shape Xml 属性详解:【Android Drawable系列】- shape xml属性详解
4.Drawable 详解:Android 中的 Drawable

GradientDrawable 解析

Shape 文件语法

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
<?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="float"
android:centerY="float"
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>

看看系统如何解析 Xml

调用路径

代码调用入口 -> 具体方法处:

1
2
3
4
5
6
7
8
9
10
11
Drawable$createFromXml(Resources r, XmlPullParser parser)

Drawable$createFromXml(Resources r, XmlPullParser parser, Theme theme)

Drawable$createFromXmlForDensity(Resources r, XmlPullParser parser, int density, Theme theme)

Drawable$createFromXmlInnerForDensity(Resources r, XmlPullParser parser, AttributeSet attrs, int density, Theme theme)

DrawableInflater$inflateFromXmlForDensity(String name, XmlPullParser parser, AttributeSet attrs, int density, Theme theme)

DrawableInflater$inflateFromTag(String name)

inflateFromXmlFor…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
// Inner classes must be referenced as Outer$Inner, but XML tag names
// can't contain $, so the <drawable> tag allows developers to specify
// the class in an attribute. We'll still run it through inflateFromTag
// to stay consistent with how LayoutInflater works.
if (name.equals("drawable")) {
name = attrs.getAttributeValue(null, "class");
if (name == null) {
throw new InflateException("<drawable> tag must specify class attribute");
}
}

Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}

initflateFromTag

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
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;
}
}

分析 GradientD… 的 inflate

调用路径

代码调用入口 -> 具体方法处:

1
2
3
GradientDrawable$inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)

GradientDrawable$inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)

inflateChildElements

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
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();

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);
}
}
}

解析的字段有很多,这里以解析 corners 为例:

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
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
});
}
}

解析 GradientDrawable

GradientState

xml 文件中解析出来的内容会存储在 GradientState 实例中,该类的结构如下:

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
final static class GradientState extends ConstantState {
public @Config int mChangingConfigurations;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public @Shape int mShape = RECTANGLE;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public @GradientType int mGradient = LINEAR_GRADIENT;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mAngle = 0;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public Orientation mOrientation;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public ColorStateList mSolidColors;
public ColorStateList mStrokeColors;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public @ColorInt int[] mGradientColors;
public @ColorInt int[] mTempColors; // no need to copy
public float[] mTempPositions; // no need to copy
@UnsupportedAppUsage
public float[] mPositions;
@UnsupportedAppUsage(trackingBug = 124050917)
public int mStrokeWidth = -1; // if >= 0 use stroking.
@UnsupportedAppUsage(trackingBug = 124050917)
public float mStrokeDashWidth = 0.0f;
@UnsupportedAppUsage(trackingBug = 124050917)
public float mStrokeDashGap = 0.0f;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public float mRadius = 0.0f; // use this if mRadiusArray is null
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public float[] mRadiusArray = null;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public Rect mPadding = null;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mWidth = -1;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mHeight = -1;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
public int mInnerRadius = -1;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
public int mThickness = -1;
public boolean mDither = false;
public Insets mOpticalInsets = Insets.NONE;

float mCenterX = 0.5f;
float mCenterY = 0.5f;
float mGradientRadius = 0.5f;
@RadiusType int mGradientRadiusType = RADIUS_TYPE_PIXELS;
boolean mUseLevel = false;
boolean mUseLevelForShape = true;

boolean mOpaqueOverBounds;
boolean mOpaqueOverShape;

ColorStateList mTint = null;
BlendMode mBlendMode = DEFAULT_BLEND_MODE;

int mDensity = DisplayMetrics.DENSITY_DEFAULT;

int[] mThemeAttrs;
int[] mAttrSize;
int[] mAttrGradient;
int[] mAttrSolid;
int[] mAttrStroke;
int[] mAttrCorners;
int[] mAttrPadding;

//....
}

映射关系

这里的映射关系是指:Xml 文件的内容和 GradientState 变量的映射关系

size 标签
1
2
3
4
5
6
7
<size 
android:width="30dp"
android:height="40dp"
/>

mWidth = width
mHeight = height
gradient 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<gradient
android:angle="45"
android:centerX="5"
android:centerY="8"
android:centerColor="@color/black"
android:endColor="#2FACEB"
android:gradientRadius="90dp"
android:startColor="#23CCC7"
android:type="linear"
android:useLevel="true"
/>

// mAngle -> mOrientation
mAngle = sWrapNegativeAngleMeasurements ? ((angle % 360) + 360) % 360 : angle % 360
mCenterX = centerX
mCenterY = centerY
mGradientColors[0] = startColor
mGradientColors[1] = hasCenterColor ? centerColor : endColor
mGradientColors[2] = hasCenterColor ? endColor : do nothing
mGradientRadius = gradientRadius
mGradient = type
mUseLevel = useLevel
solid 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<solid 
android:color="#ff00ff"
/>

mSolidColors = color //注意 这是 ColorStateList
//注意 设置了 solid 标签后 mGradientColors = null;
```


##### stroke 标签
```code
<stroke
android:width="4dp"
android:color="#00ff00"
android:dashWidth="40dp"
android:dashGap="100dp"
/>

mStrokeWidth = width
mStrokeColors = color //注意这是 ColorStateList
mStrokeDashWidth = dashWidth
mStrokeDashGap = dashGap
corners 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<corners 
android:radius="20dp"
android:topLeftRadius="30dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="30dp"
android:bottomRightRadius="40dp"
/>

mRadius = radius
mRadiusArray = new float[] {
topLeftRadius, topLeftRadius,
topRightRadius, topRightRadius,
bottomRightRadius, bottomRightRadius,
bottomLeftRadius, bottomLeftRadius
}
padding 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
<padding 
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp"
/>

mPadding = mPadding.set (
left,
top,
right,
bottom
)
shape 标签
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
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:dither="true"
android:opticalInsetLeft="10dp"
android:opticalInsetRight="10dp"
android:opticalInsetBottom="10dp"
android:opticalInsetTop="20dp"
android:tint="@color/black"
android:tintMode="src_over"
android:useLevel="false"
android:innerRadiusRatio="4"
android:thicknessRatio="10"
>

mShape = shape
mDither = dither
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mBlendMode = tintMode
} else {
mTintMode = tintMode
}
// Insets 在 Android Q 之前是 hide 的
mTint = tint
mOpticalInsets = Insets.of(
opticalInsetLeft,
opticalInsetTop,
opticalInsetRight,
opticalInsetBottom
)
if (mShape == ring) {
mInnerRadius = innerRadius
if (mInnerRadius == -1) {
mInnerRadiusRatio = innerRadiusRadio
}

mThickness = thickness
if (mThicknessRatio == -1) {
mThicknessRatio = thicknessRadio
}

mUseLevelForShape = useLevel
}

具体解析

Xml 参考
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
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>

<size
android:width="30dp"
android:height="40dp"
/>

<gradient
android:angle="45"
android:centerX="5"
android:centerY="8"
android:startColor="#23CCC7"
android:centerColor="@color/black"
android:endColor="#2FACEB"
android:gradientRadius="90dp"
android:type="radial"
android:useLevel="true"
/>

<solid
android:color="#ff00ff"
/>

<stroke android:color="#00ff00" android:width="4dp" android:dashWidth="40dp" android:dashGap="100dp"/>

<corners android:radius="20dp"
android:topLeftRadius="30dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="30dp"
android:bottomRightRadius="40dp"
/>

<padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp"/>

</shape>
解析代码

由于系统的 GradientState 无法直接访问,使用反射比较繁琐,所以这里构造一个类似的类:

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import android.content.res.ColorStateList;
import android.graphics.BlendMode;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;

import androidx.annotation.RequiresApi;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class GradientState {
public static final GradientState DEFAULT = new GradientState();

// shape 标签
// GradientDrawable 源码中没看到对 visible 子标签的解析,因此不解析这个标签
int shape = GradientDrawable.RECTANGLE;
boolean dither = false;
float innerRadiusRatio = 3.0f;
float thicknessRatio = 9.0f;
int innerRadius = -1;
int thickness = -1;
boolean useLevelForShape = true;
ColorStateList tint = null;
TintMode tintMode = TintMode.SRC_IN;
Insets opticalInsets = Insets.NONE;

// size 标签
int width = -1;
int height = -1;

// gradient 标签
int angle = 0;
float centerX = 0.5f;
float centerY = 0.5f;
int[] gradientColors = null;
float gradientRadius = 0.5f;
int gradientType = GradientDrawable.LINEAR_GRADIENT;
// 一般设为 false,否则图形不显示
// 如果值为 true ,一般是在 LevelListDrawable 中使用
boolean useLevel = false;

// solid 标签
ColorStateList solidColor = null;

// stroke 标签
int strokeWidth = -1;
ColorStateList strokeColor;
float strokeDashWidth = 0.0f;
float strokeDashGap = 0.0f;

// corners 标签
float radius = 0.0f;
float[] radiusArray = null;

// padding 标签
Rect padding = null;

public String getShapeString() {
String defaultText = "rectangle";
switch (shape) {
case GradientDrawable.RECTANGLE: return "rectangle";
case GradientDrawable.LINE: return "line";
case GradientDrawable.OVAL: return "oval";
case GradientDrawable.RING: return "ring";
default: return defaultText;
}
}

public String getGradientTypeString() {
String defaultText = "linear";
switch (gradientType) {
case GradientDrawable.LINEAR_GRADIENT: return "linear";
case GradientDrawable.RADIAL_GRADIENT: return "radial";
case GradientDrawable.SWEEP_GRADIENT: return "sweep";
default: return defaultText;
}
}

public static final class Insets {
public static final Insets NONE = new Insets(0, 0, 0, 0);
public final int left;
public final int top;
public final int right;
public final int bottom;

private Insets(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}

public static Insets parseInsets(Object obj) {
try {
Field leftField = obj.getClass().getDeclaredField("left");
leftField.setAccessible(true);
int left = (int) leftField.get(obj);

Field topField = obj.getClass().getDeclaredField("top");
topField.setAccessible(true);
int top = (int) topField.get(obj);

Field rightField = obj.getClass().getDeclaredField("right");
rightField.setAccessible(true);
int right = (int) rightField.get(obj);

Field bottomField = obj.getClass().getDeclaredField("bottom");
bottomField.setAccessible(true);
int bottom = (int) bottomField.get(obj);

return new Insets(left, top, right, bottom);
} catch (Exception e) {
return NONE;
}
}
}

public enum TintMode {
SRC_OVER,
SRC_IN,
SRC_ATOP,
MODULATE, // MULTIPLY
SCREEN,
PLUS; // ADD

private static TintMode parseMode(int mode) {
TintMode defaultMode = SRC_IN;
switch (mode) {
case 3: return SRC_OVER;
case 5: return SRC_IN;
case 9: return SRC_ATOP;
case 14: return MODULATE;
case 15: return SCREEN;
case 16: return PLUS;
default: return defaultMode;
}
}

public static TintMode parseMode(PorterDuff.Mode mode) {
TintMode defaultMode = SRC_IN;
try {
Method toValue = PorterDuff.class.getDeclaredMethod("modeToInt", PorterDuff.class);
int modeValue = (int) toValue.invoke(null, mode);
return parseMode(modeValue);
} catch (Exception e) {
return defaultMode;
}
}

@RequiresApi(api = Build.VERSION_CODES.Q)
public static TintMode parseMode(BlendMode mode) {
TintMode defaultMode = SRC_IN;
try {
Method toValue = BlendMode.class.getDeclaredMethod("toValue", BlendMode.class);
int modeValue = (int) toValue.invoke(null, mode);
return parseMode(modeValue);
} catch (Exception e) {
return defaultMode;
}
}

public String getModeString() {
switch (this) {
case SRC_OVER: return "src_over";
case SRC_IN: return "src_in";
case SRC_ATOP: return "src_atop";
case MODULATE: return "multiply";
case SCREEN: return "screen";
case PLUS: return "add";
default: return "src_in";
}
}
}
}

实际执行解析的类:GradientDrawableParser

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
import android.content.res.ColorStateList;
import android.graphics.BlendMode;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;

import com.bytedance.calidge.thirdparty.uetool.base.DimenUtil;
import com.bytedance.calidge.utils.CalidgeLogger;

import java.lang.reflect.Field;

public class GradientDrawableParser {
private static final String TAG = "xulei GradientDrawableParser";

private GradientDrawable drawable;
private Object gradientState;
private int[] stateSet;
private GradientState state;

public GradientDrawableParser(GradientDrawable drawable) {
this.drawable = drawable;
init();
}

public String parse() {
parseShape();
parseSize();
parseGradient();
parseSolid();
parseStroke();
parseCorners();
parsePadding();
return getXmlContent();
}

private void init() {
try {
Field mGradientStateField = GradientDrawable.class.getDeclaredField("mGradientState");
mGradientStateField.setAccessible(true);
gradientState = mGradientStateField.get(drawable);

Field mStateSetField = Drawable.class.getDeclaredField("mStateSet");
mStateSetField.setAccessible(true);
stateSet = (int[]) mStateSetField.get(drawable);

state = new GradientState();
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser init failure: ", e);
}
}

private void parseShape() {
try {
state.shape = (int) getStateFieldValue("mShape");
state.dither = (boolean) getStateFieldValue("mDither");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
state.tintMode = GradientState.TintMode.parseMode((BlendMode) getStateFieldValue("mBlendMode"));
} else {
state.tintMode = GradientState.TintMode.parseMode((PorterDuff.Mode) getStateFieldValue("mTintMode"));
}
state.tint = (ColorStateList) getStateFieldValue("mTint");
state.opticalInsets = GradientState.Insets.parseInsets(getStateFieldValue("mOpticalInsets"));
if (state.shape == GradientDrawable.RING) {
state.innerRadius = (int) getStateFieldValue("mInnerRadius");
if (state.innerRadius == -1) {
state.innerRadiusRatio = (float) getStateFieldValue("mInnerRadiusRatio");
}

state.thickness = (int) getStateFieldValue("mThickness");
if (state.thickness == -1) {
state.thicknessRatio = (float) getStateFieldValue("mThicknessRatio");
}

state.useLevelForShape = (boolean) getStateFieldValue("mUseLevelForShape");
}
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse shape failure: ", e);
}
}

private void parseSize() {
try {
state.width = (int) getStateFieldValue("mWidth");
state.height = (int) getStateFieldValue("mHeight");
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse size failure: ", e);
}
}

private void parseGradient() {
try {
int type = (int) getStateFieldValue("mGradient");
if (type == GradientDrawable.LINEAR_GRADIENT) {
// angle 只有在渐变类型为 linear 时才有效,默认为 0
state.angle = (int) getStateFieldValue("mAngle");
}
state.centerX = (float) getStateFieldValue("mCenterX");
state.centerY = (float) getStateFieldValue("mCenterY");
state.gradientColors = (int[]) getStateFieldValue("mGradientColors");
if (type == GradientDrawable.RADIAL_GRADIENT) {
// gradientRadius 只有在渐变类型为 radial 时才有效
state.gradientRadius = (float) getStateFieldValue("mGradientRadius");
}
state.gradientType = type;
state.useLevel = (boolean) getStateFieldValue("mUseLevel");
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse gradient failure: ", e);
}
}

private void parseSolid() {
try {
state.solidColor = (ColorStateList) getStateFieldValue("mSolidColors");
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse solid failure: ", e);
}
}

private void parseStroke() {
try {
state.strokeWidth = (int) getStateFieldValue("mStrokeWidth");
state.strokeColor = (ColorStateList) getStateFieldValue("mStrokeColors");
state.strokeDashWidth = (float) getStateFieldValue("mStrokeDashWidth");
state.strokeDashGap = (float) getStateFieldValue("mStrokeDashGap");
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse stroke failure: ", e);
}
}

private void parseCorners() {
try {
state.radius = (float) getStateFieldValue("mRadius");
state.radiusArray = (float[]) getStateFieldValue("mRadiusArray");
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse corners failure: ", e);
}
}

private void parsePadding() {
try {
state.padding = (Rect) getStateFieldValue("mPadding");
} catch (Exception e) {
CalidgeLogger.e(TAG, "GradientDrawableParser parse padding failure: ", e);
}
}

private Object getStateFieldValue(String name) throws Exception {
Field field = gradientState.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(gradientState);
}

private String getXmlContent() {
StringBuilder builder = new StringBuilder();
builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
builder.append("<shape\n");
builder.append("\txmlns:android=\"http://schemas.android.com/apk/res/android\"\n");
builder.append("\tandroid:shape=\"").append(state.getShapeString()).append("\"\n");
if (state.dither != GradientState.DEFAULT.dither) {
builder.append("\tandroid:dither=\"true\"\n");
}
if (state.opticalInsets.left != GradientState.DEFAULT.opticalInsets.left) {
builder.append("\tandroid:opticalInsetLeft=\"").append(DimenUtil.px2dip(state.opticalInsets.left)).append("dp\"\n");
}
if (state.opticalInsets.top != GradientState.DEFAULT.opticalInsets.top) {
builder.append("\tandroid:opticalInsetTop=\"").append(DimenUtil.px2dip(state.opticalInsets.top)).append("dp\"\n");
}
if (state.opticalInsets.right != GradientState.DEFAULT.opticalInsets.right) {
builder.append("\tandroid:opticalInsetRight=\"").append(DimenUtil.px2dip(state.opticalInsets.right)).append("dp\"\n");
}
if (state.opticalInsets.bottom != GradientState.DEFAULT.opticalInsets.bottom) {
builder.append("\tandroid:opticalInsetBottom=\"").append(DimenUtil.px2dip(state.opticalInsets.bottom)).append("dp\"\n");
}
if (state.tint != GradientState.DEFAULT.tint) {
builder.append("\tandroid:tint=\"").append(getColorStateListString(state.tint)).append("\"\n");
}
if (state.tintMode != GradientState.DEFAULT.tintMode) {
builder.append("\tandroid:tintMode=\"").append(state.tintMode.getModeString()).append("\"\n");
}
if (state.shape == GradientDrawable.RING) {
if (state.innerRadius != GradientState.DEFAULT.innerRadius) {
builder.append("\tandroid:innerRadius=\"").append(DimenUtil.px2dip(state.innerRadius)).append("dp\"\n");
} else {
if (state.innerRadiusRatio != GradientState.DEFAULT.innerRadiusRatio) {
builder.append("\tandroid:innerRadiusRatio=\"").append(state.innerRadiusRatio).append("\"\n");
}
}

if (state.thickness != GradientState.DEFAULT.thickness) {
builder.append("\tandroid:thickness=\"").append(DimenUtil.px2dip(state.thickness)).append("dp\"\n");
} else {
if (state.thicknessRatio != GradientState.DEFAULT.thicknessRatio) {
builder.append("\tandroid:thicknessRatio=\"").append(state.thicknessRatio).append("\"\n");
}
}

if (state.useLevelForShape != GradientState.DEFAULT.useLevelForShape) {
builder.append("\tandroid:useLevel=\"false\"");
}
}
builder.append("\t>\n");

if (hasSizeContent()) {
builder.append(getSizeContent());
}

if (hasGradientContent()) {
builder.append(getGradientContent());
}

if (hasSolidContent()) {
builder.append(getSolidContent());
}

if (hasStrokeContent()) {
builder.append(getStrokeContent());
}

if (hasCornersContent()) {
builder.append(getCornersContent());
}

if (hasPaddingContent()) {
builder.append(getPaddingContent());
}

builder.append("</shape>");

return builder.toString();
}

private boolean hasSizeContent() {
return state.width != GradientState.DEFAULT.width || state.height != GradientState.DEFAULT.height;
}

private String getSizeContent() {
StringBuilder builder = new StringBuilder();
builder.append("\t<size\n");
if (state.width != GradientState.DEFAULT.width) {
builder.append("\t\tandroid:width=\"").append(DimenUtil.px2dip(state.width)).append("dp\"\n");
}
if (state.height != GradientState.DEFAULT.height) {
builder.append("\t\tandroid:height=\"").append(DimenUtil.px2dip(state.height)).append("dp\"\n");
}
builder.append("\t\t/>\n");
return builder.toString();
}

private boolean hasGradientContent() {
if (state.angle != GradientState.DEFAULT.angle) return true;
if (state.centerX != GradientState.DEFAULT.centerX) return true;
if (state.centerY != GradientState.DEFAULT.centerY) return true;
if (state.gradientColors != null) {
for (int gradientColor : state.gradientColors) {
if (gradientColor != 0) {
return true;
}
}
}
if (state.gradientRadius != GradientState.DEFAULT.gradientRadius) return true;
if (state.gradientType != GradientState.DEFAULT.gradientType) return true;
if (state.useLevel != GradientState.DEFAULT.useLevel) return true;
return false;
}

private String getGradientContent() {
StringBuilder builder = new StringBuilder();
builder.append("\t<gradient\n");
if (state.angle != GradientState.DEFAULT.angle) {
builder.append("\t\tandroid:angle=\"").append(state.angle).append("\"\n");
}
if (state.centerX != GradientState.DEFAULT.centerX) {
builder.append("\t\tandroid:centerX=\"").append(state.centerX).append("\"\n");
}
if (state.centerY != GradientState.DEFAULT.centerY) {
builder.append("\t\tandroid:centerY=\"").append(state.centerY).append("\"\n");
}
if (state.gradientColors != null) {
if (state.gradientColors[0] != 0) {
builder.append("\t\tandroid:startColor=\"").append(getColorString(state.gradientColors[0])).append("\"\n");
}
if (state.gradientColors.length == 3) {
if (state.gradientColors[1] != 0) {
builder.append("\t\tandroid:centerColor=\"").append(getColorString(state.gradientColors[1])).append("\"\n");
}
if (state.gradientColors[2] != 0) {
builder.append("\t\tandroid:endColor=\"").append(getColorString(state.gradientColors[2])).append("\"\n");
}
} else {
if (state.gradientColors[1] != 0) {
builder.append("\t\tandroid:endColor=\"").append(getColorString(state.gradientColors[1])).append("\"\n");
}
}
}
if (state.gradientRadius != GradientState.DEFAULT.gradientRadius) {
builder.append("\t\tandroid:gradientRadius=\"").append(DimenUtil.px2dip(state.gradientRadius)).append("dp\"\n");
}
if (state.gradientType != GradientState.DEFAULT.gradientType) {
builder.append("\t\tandroid:type=\"").append(state.getGradientTypeString()).append("\"\n");
}
if (state.useLevel != GradientState.DEFAULT.useLevel) {
builder.append("\t\tandroid:useLevel=\"true\"\n");
}
builder.append("\t\t/>\n");
return builder.toString();
}

private boolean hasSolidContent() {
return state.solidColor != GradientState.DEFAULT.solidColor;
}

private String getSolidContent() {
StringBuilder builder = new StringBuilder();
builder.append("\t<solid\n");
builder.append("\t\tandroid:color=\"").append(getColorStateListString(state.solidColor)).append("\"\n");
builder.append("\t\t/>\n");
return builder.toString();
}

private boolean hasStrokeContent() {
if (state.strokeWidth != GradientState.DEFAULT.strokeWidth) return true;
if (state.strokeColor != GradientState.DEFAULT.strokeColor) return true;
if (state.strokeDashWidth != GradientState.DEFAULT.strokeDashWidth) return true;
if (state.strokeDashGap != GradientState.DEFAULT.strokeDashGap) return true;
return false;
}

private String getStrokeContent() {
StringBuilder builder = new StringBuilder();
builder.append("\t<stroke\n");
if (state.strokeWidth != GradientState.DEFAULT.strokeWidth) {
builder.append("\t\tandroid:width=\"").append(DimenUtil.px2dip(state.strokeWidth)).append("dp\"\n");
}
if (state.strokeColor != GradientState.DEFAULT.strokeColor) {
builder.append("\t\tandroid:color=\"").append(getColorStateListString(state.strokeColor)).append("\"\n");
}
if (state.strokeDashWidth != GradientState.DEFAULT.strokeDashWidth) {
builder.append("\t\tandroid:dashWidth=\"").append(DimenUtil.px2dip(state.strokeDashWidth)).append("dp\"\n");
}
if (state.strokeDashGap != GradientState.DEFAULT.strokeDashGap) {
builder.append("\t\tandroid:dashGap=\"").append(DimenUtil.px2dip(state.strokeDashGap)).append("dp\"\n");
}
builder.append("\t\t/>\n");
return builder.toString();
}

private boolean hasCornersContent() {
if (state.radius != GradientState.DEFAULT.radius) return true;
if (state.radiusArray != null) {
for (float item : state.radiusArray) {
if (item != state.radius) {
return true;
}
}
}
return false;
}

private String getCornersContent() {
StringBuilder builder = new StringBuilder();
builder.append("\t<corners\n");
if (state.radius != GradientState.DEFAULT.radius) {
builder.append("\t\tandroid:radius=\"").append(DimenUtil.px2dip(state.radius)).append("dp\"\n");
}
if (state.radiusArray != null) {
if (state.radiusArray[0] != state.radius) {
builder.append("\t\tandroid:topLeftRadius=\"").append(DimenUtil.px2dip(state.radiusArray[0])).append("dp\"\n");
}
if (state.radiusArray[2] != state.radius) {
builder.append("\t\tandroid:topRightRadius=\"").append(DimenUtil.px2dip(state.radiusArray[2])).append("dp\"\n");
}
if (state.radiusArray[4] != state.radius) {
builder.append("\t\tandroid:bottomRightRadius=\"").append(DimenUtil.px2dip(state.radiusArray[4])).append("dp\"\n");
}
if (state.radiusArray[6] != state.radius) {
builder.append("\t\tandroid:bottomLeftRadius=\"").append(DimenUtil.px2dip(state.radiusArray[6])).append("dp\"\n");
}
}
builder.append("\t\t/>\n");
return builder.toString();
}

private boolean hasPaddingContent() {
if (state.padding != null) {
return state.padding.left != 0 || state.padding.top != 0
|| state.padding.right != 0
|| state.padding.bottom != 0;
}
return false;
}

private String getPaddingContent() {
StringBuilder builder = new StringBuilder();
builder.append("\t<padding\n");
if (state.padding != null) {
if (state.padding.left != 0) {
builder.append("\t\tandroid:left=\"").append(DimenUtil.px2dip(state.padding.left)).append("dp\"\n");
}
if (state.padding.top != 0) {
builder.append("\t\tandroid:top=\"").append(DimenUtil.px2dip(state.padding.top)).append("dp\"\n");
}
if (state.padding.right != 0) {
builder.append("\t\tandroid:right=\"").append(DimenUtil.px2dip(state.padding.right)).append("dp\"\n");
}
if (state.padding.bottom != 0) {
builder.append("\t\tandroid:bottom=\"").append(DimenUtil.px2dip(state.padding.bottom)).append("dp\"\n");
}
}
builder.append("\t\t/>\n");
return builder.toString();
}

private String getColorStateListString(ColorStateList colorStateList) {
return "#" + Integer.toHexString(colorStateList.getColorForState(stateSet, 0));
}

private String getColorString(int color) {
return "#" + Integer.toHexString(color);
}
}

如何使用

1
2
GradientDrawable drawable = .....
String xml = GradientDrawableParser(drawable).parse()