Android中的艺术字
LloydFinch 10/18/2021 AndroidView
# 效果
使用代码如下:
<com.lloydfinch.mooneffect.NumberView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FF0000"
android:padding="8dp"
app:nv_num0="@drawable/ic_0"
app:nv_num1="@drawable/ic_1"
app:nv_num2="@drawable/ic_2"
app:nv_num3="@drawable/ic_3"
app:nv_num4="@drawable/ic_4"
app:nv_num5="@drawable/ic_5"
app:nv_num6="@drawable/ic_6"
app:nv_num7="@drawable/ic_7"
app:nv_num8="@drawable/ic_8"
app:nv_num9="@drawable/ic_9"
app:nv_number="12345689" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
通过nv_num指定对应的数字图片,然后通过nv_number指定数字即可。
# 分析&代码
我们不可能绘制出这样的文字,太费劲了,所以我们可以使用0-9这10张数字图片 跟 数字建立个对应关系,然后根据数字去找图片 并 绘制出来即可。
所以我们需要:
- 1 提供0-9共10张数字图片。
- 2 建立每一位数字到图片的映射。
- 3 将数字拆解成一位一位的,并根据每一位找到对应的图片。
- 4 将对应的图片绘制出来即可。
好,逻辑有了,接下来就是实现:
- 1 我们提供ic_0到ic_9这10张数字图,并且我们定义一个style让用户可以手动指定这些图片。
<!--自定义数字图片-->
<declare-styleable name="NumberView">
<!--要展示的数字-->
<attr name="nv_number" format="string" />
<!--数字们对应的图片们-->
<attr name="nv_num1" format="reference" />
<attr name="nv_num2" format="reference" />
<attr name="nv_num3" format="reference" />
<attr name="nv_num4" format="reference" />
<attr name="nv_num5" format="reference" />
<attr name="nv_num6" format="reference" />
<attr name="nv_num7" format="reference" />
<attr name="nv_num8" format="reference" />
<attr name="nv_num9" format="reference" />
<attr name="nv_num0" format="reference" />
</declare-styleable>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 2 我们建立一个Map来存储数字到图片的映射关系:
// 存放数字对应的图片
private Map<Integer, Integer> numsMap = new HashMap<>();
// 将用户在xml中指定的数字图片和数字建立映射
numsMap.put(0, array.getResourceId(R.styleable.NumberView_nv_num0, 0));
numsMap.put(1, array.getResourceId(R.styleable.NumberView_nv_num1, 0));
numsMap.put(2, array.getResourceId(R.styleable.NumberView_nv_num2, 0));
numsMap.put(3, array.getResourceId(R.styleable.NumberView_nv_num3, 0));
numsMap.put(4, array.getResourceId(R.styleable.NumberView_nv_num4, 0));
numsMap.put(5, array.getResourceId(R.styleable.NumberView_nv_num5, 0));
numsMap.put(6, array.getResourceId(R.styleable.NumberView_nv_num6, 0));
numsMap.put(7, array.getResourceId(R.styleable.NumberView_nv_num7, 0));
numsMap.put(8, array.getResourceId(R.styleable.NumberView_nv_num8, 0));
numsMap.put(9, array.getResourceId(R.styleable.NumberView_nv_num9, 0));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 3 将数字拆解成一位一位的,并找到每位数字对应的图片保存下来,已备绘制,这里要注意顺序。
private void refreshNumber() {
int length = text.length();
// 创建数字对应的图片数组
pics = new Bitmap[length];
try {
// 倒序把数字对应的图片存放到pics里面
int number = Integer.parseInt(text);
while (number != 0) {
// 我们这里是后往前依次取个位数,所以要从后往前放图片
pics[--length] = BitmapFactory.decodeResource(getResources(), numsMap.get(number % 10));
number /= 10;
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 4 绘制图片,这里要考虑padding。
@Override
protected void onDraw(Canvas canvas) {
float left = getPaddingStart();
float top = getPaddingTop();
// 直接遍历图片并从左到右绘制出来
for (Bitmap pic : pics) {
canvas.drawBitmap(pic, left, top, mPaint);
left += pic.getWidth();
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
这里有个注意点,就是需要处理onMeasure()函数,防止用户给定的数字图片不规则的情况。
完整的代码如下所示:
public class NumberView extends View {
// 显示的数字
private String text = "0";
private Paint mPaint;
// 数字-图片 映射
private Map<Integer, Integer> numsMap = new HashMap<>();
// 用来存放需要绘制的图片
private Bitmap[] pics;
public NumberView(Context context) {
this(context, null);
}
public NumberView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.NumberView);
text = array.getString(R.styleable.NumberView_nv_number);
numsMap.put(0, array.getResourceId(R.styleable.NumberView_nv_num0, 0));
numsMap.put(1, array.getResourceId(R.styleable.NumberView_nv_num1, 0));
numsMap.put(2, array.getResourceId(R.styleable.NumberView_nv_num2, 0));
numsMap.put(3, array.getResourceId(R.styleable.NumberView_nv_num3, 0));
numsMap.put(4, array.getResourceId(R.styleable.NumberView_nv_num4, 0));
numsMap.put(5, array.getResourceId(R.styleable.NumberView_nv_num5, 0));
numsMap.put(6, array.getResourceId(R.styleable.NumberView_nv_num6, 0));
numsMap.put(7, array.getResourceId(R.styleable.NumberView_nv_num7, 0));
numsMap.put(8, array.getResourceId(R.styleable.NumberView_nv_num8, 0));
numsMap.put(9, array.getResourceId(R.styleable.NumberView_nv_num9, 0));
array.recycle();
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
refreshNumber();
}
private void refreshNumber() {
// 初始化图片数组
int length = text.length();
pics = new Bitmap[length];
try {
// 倒序把数字对应的图片存放到pics里面
int number = Integer.parseInt(text);
while (number != 0) {
pics[--length] = BitmapFactory.decodeResource(getResources(), numsMap.get(number % 10));
number /= 10;
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 如果宽度是wrap_content,就取图片的总宽度
if (widthMeasureMode == MeasureSpec.AT_MOST) {
widthSize = 0;
widthSize += (getPaddingStart() + getPaddingEnd());
for (Bitmap pic : pics) {
widthSize += pic.getWidth();
}
}
// 如果高度是wrap_content,就取所有图片中的最大高度
if (heightMeasureMode == MeasureSpec.AT_MOST) {
heightSize = 0;
heightSize += (getPaddingTop() + getPaddingBottom());
int maxHeight = 0;
for (Bitmap pic : pics) {
maxHeight = Math.max(pic.getHeight(), maxHeight);
}
heightSize += maxHeight;
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(widthSize, widthMeasureMode), MeasureSpec.makeMeasureSpec(heightSize, heightMeasureMode));
}
@Override
protected void onDraw(Canvas canvas) {
float left = getPaddingStart();
float top = getPaddingTop();
// 直接遍历图片并从左到右绘制出来
for (Bitmap pic : pics) {
canvas.drawBitmap(pic, left, top, mPaint);
left += pic.getWidth();
}
}
/**
* 设置数字
*
* @param text 要设置的数字
*/
public void setText(String text) {
this.text = text;
refreshNumber();
}
}
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
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
# 总结
核心思想就一个: 这个玩意儿一看就不是一时半会画出来的,就不要再纠结,就算最后终于画出来了,也就是装个X,浪费时间精力。说白了就是不做"收支比"不高的事。
知识点也是一个: 对直接继承自View的自定义View,需要手动处理wrap_content和padding这两个关键点。