View的绘制流程:
onMeasure() // 一般进行计算测量视图宽和高等
onLayout() // 进行布局,分别size和位置给视图
onDraw() // 绘制视图view
我们经常遇到应用中需要显示一个进度比或使用情况占比的圆形视图,中间显示内容和进度比。
实现思路:
自定义一个View,使用两个Paint 画笔分别用来画圆形视图的背景和使用进度;再使用两个TextPaint 分别用来画内容、进度比文本。
public class DonutView extends View {
private static final int TOP = 90;
private float mStrokeWidth;
private float mDeviceDensity;
private int mPercent;
private Paint mBackgroundCircle;
private Paint mFilledArc;
private TextPaint mTextPaint;
private TextPaint mBigNumberPaint;
private String mPercentString;
private String mDescriptionString;
private static final int METER_BG_COLOR = Color.parseColor("#ffd1d1d1");
private static final int[] METER_CONSUMED_COLOR = new int[] {
Color.parseColor("#ff69bd2e"),
Color.parseColor("#ffff6f26"),
Color.parseColor("#ffff5147"),
};
private final int GREEN_PERCENT = 60;
private final int ORANGE_PERCENT = 85;
public DonutView(Context context) {
super(context);
}
public DonutView(Context context, AttributeSet attrs) {
super(context, attrs);
mDeviceDensity = getResources().getDisplayMetrics().density;
mStrokeWidth = 6f * mDeviceDensity;
mBackgroundCircle = new Paint();
mBackgroundCircle.setAntiAlias(true);
mBackgroundCircle.setStrokeCap(Paint.Cap.BUTT);
mBackgroundCircle.setStyle(Paint.Style.STROKE);
mBackgroundCircle.setStrokeWidth(mStrokeWidth);
mBackgroundCircle.setColor(METER_BG_COLOR);
mFilledArc = new Paint();
mFilledArc.setAntiAlias(true);
mFilledArc.setStrokeCap(Paint.Cap.BUTT);
mFilledArc.setStyle(Paint.Style.STROKE);
mFilledArc.setStrokeWidth(mStrokeWidth);
mTextPaint = new TextPaint();
mTextPaint.setColor(R.color.donut_text_color);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(12f * mDeviceDensity);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mBigNumberPaint = new TextPaint();
mBigNumberPaint.setColor(Color.BLACK);
mBigNumberPaint.setAntiAlias(true);
mBigNumberPaint.setTextSize(24f * mDeviceDensity);
mBigNumberPaint.setTextAlign(Paint.Align.CENTER);
setPercentage(0);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawDonut(canvas);
drawInnerText(canvas);
}
private void drawDonut(Canvas canvas) {
canvas.drawArc(
0 + mStrokeWidth,
0 + mStrokeWidth,
getWidth() - mStrokeWidth,
getHeight() - mStrokeWidth,
TOP,
360,
false,
mBackgroundCircle);
canvas.drawArc(
0 + mStrokeWidth,
0 + mStrokeWidth,
getWidth() - mStrokeWidth,
getHeight() - mStrokeWidth,
TOP,
(360 * mPercent / 100),
false,
mFilledArc);
}
private void drawInnerText(Canvas canvas) {
final float centerX = getWidth() / 2;
final float centerY = getHeight() / 2;
final float totalHeight = getTextHeight(mTextPaint) + getTextHeight(mBigNumberPaint);
final float startY = centerY + totalHeight / 2;
if(mPercentString == null){
return;
}
// The first line is the height of the bottom text + its descender above the bottom line.
canvas.drawText(mPercentString, centerX,
startY - getTextHeight(mTextPaint) - mBigNumberPaint.descent(),
mBigNumberPaint);
// The second line starts at the bottom + room for the descender.
CharSequence mCharSequence = mDescriptionString;
mDescriptionString = TextUtils.ellipsize(mCharSequence,mTextPaint,getWidth()-60,TextUtils.TruncateAt.END).toString();
canvas.drawText(mDescriptionString, centerX, startY - mTextPaint.descent(), mTextPaint);
}
/**
* Set a percentage full to have the donut graph.
*/
public void setPercentage(int percent) {
mPercent = percent;
int i = 0;
if(mPercent < GREEN_PERCENT){
i = 0;
}else if((mPercent >= GREEN_PERCENT) && (mPercent < ORANGE_PERCENT)){
i = 1;
}else if(mPercent >= ORANGE_PERCENT){
i = 2;
}
mFilledArc.setColor(METER_CONSUMED_COLOR[i]);
mPercentString = formatPercentage(mPercent);
invalidate();
}
public void setVolDescription(String description) {
if(description != null){
mDescriptionString = description;
invalidate();
}
}
private float getTextHeight(TextPaint paint) {
// Technically, this should be the cap height, but I can live with the descent - ascent.
return paint.descent() - paint.ascent();
}
public static String formatPercentage(int percentage) {
return formatPercentage(((double) percentage) / 100.0);
}
private static String formatPercentage(double percentage) {
return NumberFormat.getPercentInstance().format(percentage);
}
}