0%

Android自定义View(一)

前言

之前在微信公众号上看到这篇文章,实现了下图的效果,正好最近在学自定义View,正好可以练练手

图1

分析

思路1

从最开始的元素入手,算出包含文字矩形大小(Paint.getTextBounds()
),根据坐标先画出左半圆,再画两条中间线,最后画出右半圆,逐渐减小中间线长度(开启线程定时更改),变成圆后再用Canvas.drawArc()滚动的扇形条。其实一开始我也是这么做的,虽然也能实现同样的效果,但是比较复杂。思路1优点是容易想到,缺点是实现起来比较复杂,坐标换算麻烦。

思路2

思路2是公众号上提供的,直接用Canvas.drawRoundText画出圆角矩形,然后在中间写上字即可,动画效果直接使用ValueAnimator 即可实现数值变化。对比来看,思路2比较好实现。

实现

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
package com.nancyyihao.demo;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.LinearInterpolator;
public class LoginView extends View {
private int mWidth ;
private int mHeight ;
private String mTestText ="testing";
private final static int DEFAULT_WIDTH = 200 ;
private final static int DEFAULT_HEIGHT = 100 ;
private float startAngle = 0;
private float sweepAngle = 45 ;
private int mStrokeWidth = 5;
private int mDeltaWidth = 0;
private int mPadding = 5 ;
private Paint mRoundRectPaint;
private TextPaint mTextPaint;
private RectF mRectF ;
private ValueAnimator animator ;
public LoginView(Context context) {
super(context);
init();
}
public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoginView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mRoundRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mRoundRectPaint.setColor(Color.parseColor("#FF4081"));
mRoundRectPaint.setStyle(Paint.Style.STROKE);
mRoundRectPaint.setStrokeWidth(mStrokeWidth);
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(Color.BLUE);
mTextPaint.setTextSize(30);
mTextPaint.setStrokeWidth(mStrokeWidth);
mRectF = new RectF();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = getSize(widthMeasureSpec, DEFAULT_WIDTH);
mHeight = getSize(heightMeasureSpec, DEFAULT_HEIGHT);
setMeasuredDimension(mWidth, mHeight);
}
private int getSize(int measureSpec, int defVaule) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
return size ;
case MeasureSpec.AT_MOST:
return Math.min(size, defVaule) ;
}
return defVaule ;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRectF = getRectF(mDeltaWidth + mPadding, 0 + mPadding, mWidth - mDeltaWidth - mPadding, mHeight - mPadding) ;
Paint.FontMetricsInt fontMetrics = mRoundRectPaint.getFontMetricsInt();
//文字垂直居中
int baseline =(int) (mRectF.bottom + mRectF.top - fontMetrics.bottom - fontMetrics.top) / 2;
mRoundRectPaint.setColor(getResources().getColor(R.color.colorAccent));
canvas.drawRoundRect(mRectF, mHeight / 2, mHeight / 2, mRoundRectPaint);
if (mDeltaWidth < mWidth/2 - mHeight / 2) {
if (mDeltaWidth == 0) {
canvas.drawText(mTestText, mWidth / 2, baseline , mTextPaint);
}
} else {
mRoundRectPaint.setColor(Color.BLUE);
canvas.drawArc(getRectF(mWidth/2 - mHeight/2 + mPadding, 0 + mPadding, mWidth/2 + mHeight/2 - mPadding, mHeight - mPadding), startAngle, sweepAngle, false, mRoundRectPaint);
}
}
private RectF getRectF(float left, float top, float right, float bottom) {
mRectF.set(left, top, right, bottom);
return mRectF ;
}
public void doSmallAnimate() {
if (animator != null) {
animator.cancel();
}
animator = ValueAnimator.ofInt(0, mWidth/2 - mHeight / 2);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDeltaWidth = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animator.removeAllListeners();
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
doSpin();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator(1.0f));
animator.start();
}
private void doSpin() {
if (animator != null) {
animator.cancel();
}
animator = ValueAnimator.ofFloat(0, 360);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startAngle = (float) animation.getAnimatedValue();
postInvalidate();
}
});
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
}

基本上是公众号上的思路,按照思路2,自己把代码敲了一遍,加了一些优化。基本上是一样的。这里没有实现由小变大的过程(大变小过程反过来即可)。难点主要在坐标的计算上,耐心把坐标计算出来就没啥问题。需要注意的一点是如果没有加上padding的话会造成图形被截掉,如下图很不好看。

图2

延伸阅读

android canvas drawText()文字居中
Android Canvas drawText实现中文垂直居中
Android Center text on canvas