Android自定义控件经常会用到Canvas绘制2D图形,在优化自己自定义控件技能之前,必须熟练掌握Canvas绘图机制。本文从以下三个方面对Canvas绘图机制进行讲解:
首先,来看一下Android官网对Canvas类的定义:
The Canvas class holds the “draw” calls。To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls(writing into the bitmap), a drawing primitive(eg, Rect, Path, text, Bitmap), and a paint(to describe the colors and styles for the drawing).
简单来说,Android进行2D绘图必须要有Canvas类的支持,它位于“android.graphics.Canvas”包下。我们可以把Canvas理解成系统分配给我们的一块用于绘图的内存(ps:真正的内存是其包含的Bitmap)。
Canvas提供了两个构造函数:
通常,我们会采用第二种包含Bitmap参数的构造函数方式或者直接使用onDraw方法中系统提供的Canvas。
既然Canvas主要用于绘图,那么它提供了很多相应的draw方法,方便我们在Canvas对象上绘图,介绍几个常用的draw方法:
从上面列举的几个Canvas.drawXXX()方法可以看到,其中都有一个类型为Paint的参数,可以把它理解成为”画笔”,通过这个画笔,在Canvas这张画布上作画。它位于“android.graphics.Paint”包下,主要用于设置绘图风格,包括画笔颜色。
Paint中提供了大量设置绘画风格的方法,这里仅列出一些常用的功能:
通过效果图,我们首先抽象出自定义属性,如下:
在Android中,可以在项目的res/values/目录下,建立一个resources源文件,通过declare-styleable来声明一个特定的属性集合。
示例属性集合如下所示(res/values/attrs_round_progress_bar.xml):
<resources>
<declare-styleable name="RoundProgressBar">
<attr name="startAngle" format="integer"></attr>
<attr name="radius" format="dimension"></attr>
<attr name="ringWidth" format="dimension"></attr>
<attr name="centerColor" format="color"></attr>
<attr name="ringColor" format="color"></attr>
<attr name="progressColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="textColor" format="color"></attr>
<attr name="isTextDisplay" format="boolean"></attr>
</declare-styleable>
</resources>
自定义的属性可以在自定义View的构造函数中,通过TypedArray数组获取,我们来自定义一个圆形的View来实现上图的效果(RoundProgressBar.java):
package love.com.progressbar.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import love.com.progressbar.R;
public class RoundProgressBar extends View {
private static final int START_ANGLE = -90;
private static final String CENTER_COLOR = "#eeff06";
private static final String RING_COLOR = "#FF7281E1";
private static final String PROGRESS_COLOR = "#FFDA0F0F";
private static final String TEXT_COLOR = "#FF000000";
private static final int TEXT_SIZE = 30;
private static final int CIRCLE_RADIUS = 20;
private static final int RING_WIDTH = 5;
/** * 圆弧的起始角度,参考canvas.drawArc方法 */
private int startAngle;
/** * 圆形内半径 */
private int radius;
/** * 进度条的宽度 */
private int ringWidth;
/** * 默认进度 */
private int mProgress = 0;
/** * 圆形内部填充色 */
private int centerColor;
/** * 进度条背景色 */
private int ringColor;
/** * 进度条的颜色 */
private int progressColor;
/** * 文字大小 */
private int textSize;
/** * 文字颜色 */
private int textColor;
/** * 文字是否需要显示 */
private boolean isTextDisplay;
private String textContent;
private Paint mPaint;
public RoundProgressBar(Context context) {
this(context, null);
}
public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
for (int i = 0; i < a.length(); i ++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.RoundProgressBar_startAngle:
startAngle = a.getInteger(attr, START_ANGLE);
break;
case R.styleable.RoundProgressBar_centerColor:
centerColor = a.getColor(attr, Color.parseColor(CENTER_COLOR));
break;
case R.styleable.RoundProgressBar_progressColor:
progressColor = a.getColor(attr, Color.parseColor(PROGRESS_COLOR));
break;
case R.styleable.RoundProgressBar_ringColor:
ringColor = a.getColor(attr, Color.parseColor(RING_COLOR));
break;
case R.styleable.RoundProgressBar_textColor:
textColor = a.getColor(attr, Color.parseColor(TEXT_COLOR));
break;
case R.styleable.RoundProgressBar_textSize:
textSize = (int) a.getDimension(attr, TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, TEXT_SIZE,
getResources().getDisplayMetrics()));
break;
case R.styleable.RoundProgressBar_isTextDisplay:
isTextDisplay = a.getBoolean(attr, true);
break;
case R.styleable.RoundProgressBar_radius:
radius = (int) a.getDimension(attr, TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, CIRCLE_RADIUS,
getResources().getDisplayMetrics()
));
break;
case R.styleable.RoundProgressBar_ringWidth:
ringWidth = (int) a.getDimension(attr, TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, RING_WIDTH,
getResources().getDisplayMetrics()
));
break;
default:
break;
}
}
a.recycle();
// 初始化画笔设置
setPaint();
}
private void setPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取圆心坐标
int cx = getWidth() / 2;
int cy = cx;
/** * 画圆心颜色 */
if (centerColor != 0) {
drawCenterCircle(canvas, cx, cy);
}
/** * 画外层大圆 */
drawOuterCircle(canvas, cx, cy);
/** * 画进度圆弧 */
drawProgress(canvas, cx, cy);
/** * 画出进度百分比 */
drawProgressText(canvas, cx, cy);
}
private void drawProgressText(Canvas canvas, int cx, int cy) {
if (!isTextDisplay) {
return;
}
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
mPaint.setStrokeWidth(0);
textContent = getProgress() + "%";
float textWidth = mPaint.measureText(textContent);
canvas.drawText(textContent, cx - textWidth / 2, cy + textSize / 2, mPaint);
}
private void drawProgress(Canvas canvas, int cx, int cy) {
mPaint.setColor(progressColor);
mPaint.setStrokeWidth(ringWidth);
mPaint.setStyle(Paint.Style.STROKE);
RectF mRectF = new RectF(cx - radius, cy - radius, cx + radius, cy + radius);
float sweepAngle = (float) (mProgress * 360.0 / 100);
canvas.drawArc(mRectF, startAngle, sweepAngle, false, mPaint);
}
private void drawOuterCircle(Canvas canvas, int cx, int cy) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(ringColor);
mPaint.setStrokeWidth(ringWidth);
canvas.drawCircle(cx, cy, radius, mPaint);
}
private void drawCenterCircle(Canvas canvas, int cx, int cy) {
mPaint.setColor(centerColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(cx, cy, radius, mPaint);
}
public synchronized int getProgress() {
return mProgress;
}
public synchronized void setProgress(int progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 100) {
progress = 100;
}
mProgress = progress;
// 进度改变时,需要通过invalidate方法进行重绘
postInvalidate();
}
}
在MainActivity.java的布局文件中,可以这样调用圆形进度条:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:round="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent">
<love.com.progressbar.view.RoundProgressBar android:id="@+id/id_round_progressbar" android:layout_width="400dp" android:layout_height="400dp" round:radius="100dp" round:ringWidth="20dp" round:startAngle="-90" round:centerColor="#eeff06" round:ringColor="#e16556e6" round:progressColor="#d20c0c" round:textColor="#000000" round:textSize="20sp" round:isTextDisplay="true"/>
</RelativeLayout>
其中,xmlns:round=”http://schemas.android.com/apk/res-auto是Android Studio中增加的导入自定义View属性的命名空间写法。