一、Canvas 简介
Canvas 翻译过来为画布意思。这好比我们在学画画时,首先要有一块画板,然后使用各种彩色笔在画板上绘制各种各样的图形,最终会呈现给我们一幅优美的画。在这里,Canvas 就相当于画板,我们可以使用画笔在 Canvas 绘制各种各样的图形,最终画出我们想要的图形。
Canvas 是 Android 2D 绘制图形的继承,提供丰富的 API 接口,功能非常强大。根据官方文档的介绍,要绘制某些东西需要 4 中基本组件:
持有像素的位图(Bitmap);
画布(Canvas);
绘图元素(Rect);
画笔(Paint)。
二、Canvas API 简介
操作类型 | 相关 API | API 解释 |
---|---|---|
绘制颜色 | drawARGB(int a, int r, int g, int b) | 使用 ARGB 填充整块画布 |
drawColor(int color) | 使用颜色填充整块画布 | |
drawColor(int color, PorterDuff.Mode mode) | 使用颜色和 PorterDuff.Mode 填充整块画布 | |
drawRGB(int r, int g, int b) | 使用 RGB 填充整块画布 | |
绘制点 | drawPoint(float x, float y, Paint paint) | 绘制单个点 |
drawPoints(float[] pts, Paint paint) | 绘制一组点 | |
drawPoints(float[] pts, int offset, int count, Paint paint) | 绘制一系列点 | |
绘制直线 | drawLine(float startX, float startY, float stopX, float stopY, Paint paint) | 使用画笔绘制直线 |
drawLines(float[] pts, Paint paint) | 使用画笔绘制一组直线 | |
drawLines(float[] pts, int offset, int count, Paint paint) | 使用画笔绘制一系列直线 | |
绘制矩形 | drawRect(float left, float top, float right, float bottom, Paint paint) | 使用画笔绘制矩形 |
drawRect(Rect r, Paint paint) | ||
drawRect(RectF rect, Paint paint) | ||
绘制圆角矩形 | drawRoundRect(RectF rect, float rx, float ry, Paint paint) | 使用画笔绘制圆角矩形 |
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)(Android 5.0 及以上才有) | ||
绘制椭圆 | drawOval(float left, float top, float right, float bottom, Paint paint)(Android 5.0 及以上才有) | 使用画笔绘制椭圆 |
drawOval(RectF oval, Paint paint) | ||
绘制圆 | drawCircle(float cx, float cy, float radius, Paint paint) | 使用画笔绘制圆 |
绘制圆弧 | drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) | 使用画笔绘制圆弧 |
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)(Android 5.0 及以上才有) |
以上只是部分 API 介绍,随着自己进一步学习,会继续完善。
三、Canvas 用法
以上介绍部分 Canvas API,那么接下来的任务是学习如何使用相关 API。
1、绘制颜色
Canvas API 提供 4 个方法用于绘制颜色,即用某种颜色填充整块画布。
canvas.drawARGB(225, 0, 255, 0);
canvas.drawColor(Color.GREEN);
canvas.drawRGB(0, 255, 0);
canvas.drawColor(Color.GREEN, PorterDuff.Mode.LIGHTEN);
需要注意的是绘制颜色第 4 种方法(即第 7 行代码)中第二个参数 PorterDuff.Mode,具体请参考官网。(ps:这知识点还不是很了解,有时间再研究。)
对于以下各种图形的绘制,都需要画笔,那么就先来创建画笔。
public class CustomView extend View {
private Paint mPaint;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.GREEN); // 设置画笔颜色
mPaint.setStrokeWidth(2); // 设置画笔宽度
mPaint.setStyle(Paint.Style.STROKE); // 设置画笔风格(描边)
}
}
2、绘制点
Canvas API 提供 3 个方法用于绘制单个点、一组点以及一系列点。
// 由于点形状比较小,不容易看清,所以将画笔宽度设置大点
mPaint.setStrokeWidth(10);
// 绘制单个点,画笔颜色为 Color.GREEN
canvas.drawPoint(300, 300, mPaint);
//绘制一组点,画笔颜色为 Color.RED
mPaint.setColor(Color.RED);
canvas.drawPoints(new float[] {400, 400, 300, 500, 500, 500}, mPaint);
有没有发现点的形状是正方形,这是因为 Paint 默认 Paint.Cap 属性是 Paint.Cap.SQUARE。如果要修改该属性,可以调用 Paint 方法 setStrokeCap(Paint.Cap cap) 修改。
3、绘制直线
Canvas 提供 3 个方法用于绘制单条直线、一组直线和一系列直线。
mPaint.setStrokeWidth(2);
// 绘制单条直线,画笔颜色为 Color.GREEN
mPaint.setColor(Color.GREEN);
canvas.drawPoint(300, 300, mPaint);
// 绘制一组直线,画笔颜色为 Color.RED
mPaint.setColor(Color.RED);
canvas.drawLines(new float[] {400, 400, 300, 500, 300, 500, 500, 500, 500, 500, 400, 400}, mPaint);
// 绘制一系列直线,画笔颜色为 Color.MAGENTA
mPaint.setColor(Color.MAGENTA);
canvas.drawLines(new float[] {400, 250, 300, 350, 300, 350, 500, 350, 500, 350, 400, 250}, 0, 12, mPaint);
看下绘制直线第三种方法,即第 13 行代码,方法原型为:drawLines(float[] pts, int offset, int count, Paint paint)
pts:绘制直线所需要的点;
offset:跳过点个数,这些点不参与绘制;
count:实际参与绘制个数;
paint:画笔。
该方法相对比较灵活,可以通过参数 offset、count 设置来决定绘制哪些线段。
4、绘制矩形
Canvas API 提供 3 个方法用于绘制矩形。绘制矩形一般只需要两个点就可以绘制,即左上角和右下角这两个点坐标即可。
Rect rect = new Rect(getWidth() / 8, getHeight() / 8 + 500, getWidth() / 8 * 7, getHeight() / 8 * 3 + 500);
RectF rectF = new RectF(getWidth() / 8, getHeight() / 8 + 200, getWidth() / 8 * 7, getHeight() / 8 * 3 + 200);
mPaint.setColor(Color.GREEN);
canvas.drawRect(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getWidth() / 8 * 3, mPaint);
mPaint.setColor(Color.MAGENTA);
canvas.drawRect(rectF, mPaint);
mPaint.setColor(Color.RED);
canvas.drawRect(rect, mPaint);
5、绘制圆角矩形
Canvas API 提供 2 种方法用于绘制圆角矩形,其中有一种方法是 Android 5.0 以上才提供,见以上 API 介绍。
// 第一种绘制圆角矩形方法
mPaint.setStrokeWidth(5);
RectF rect = new RectF(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3);
canvas.drawRoundRect(rect, 50, 50, mPaint);
// 第二种绘制圆角矩形方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3, 50, 50, mPaint);
}
6、绘制椭圆
Canvas API 提供 2 种方法用于绘制椭圆,其中有一种方法是 Android 5.0 以上才提供,见以上 API 介绍。其实椭圆是被包裹在矩形内,话句话说,是矩形的内切图形。绘制椭圆也很容易,只需传入矩形 RectF 和绘制图形 Paint 就可以。
// 第一种绘制椭圆方法
RectF rect = new RectF(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3);
canvas.drawLine(getWidth() / 8, getHeight() / 4, getWidth() / 8 * 7, getHeight() / 4, mPaint);
canvas.drawLine(getWidth() / 2, getHeight() / 8, getWidth() / 2, getHeight() / 8 * 3, mPaint);
canvas.drawRect(rect, mPaint);
canvas.drawOval(rect, mPaint);
// 第二种绘制椭圆方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawOval(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3, mPaint);
}
以上多绘制出矩形和两条直线,主要是为了方便看清。
7、绘制圆形
Canvas API 提供 1 种方法用于绘制圆形。绘制圆形只需要知道圆心和半径。
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 200, mPaint);
mPaint.setStrokeWidth(5);
mPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawPoint(getWidth() / 2, getHeight() / 2, mPaint);
这里我把圆心也给画出来了,画笔的宽度设置为 5,容易看清。
8、绘制圆弧
Canvas API 提供 2 种方法用于绘制圆弧,其中有一种方法是 Android 5.0 以上才提供,见以上 API 介绍。有必要来解释下这两种方法各个参数的含义,以drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 为例子:
oval:矩形;
startAngle:起始角度;
sweepAngle:绘制圆弧的角度,即扫描过的角度;
useCenter:true 或者 false,至于区别通过例子来说明。
paint:画笔。
// userCenter 为 true
RectF rectF = new RectF(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3);
canvas.drawRect(rectF, mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawArc(rectF, 0, 90, true, mPaint);
canvas.translate(getWidth() / 16, getHeight() / 3);
// userCenter 为 false
mPaint.setStyle(Paint.Style.STROKE);
RectF rect = new RectF(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3);
canvas.drawRect(rect, mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawArc(rect, 0, 90, false, mPaint);
// 第二种方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc(getWidth() / 8, getHeight() / 8, getWidth() / 8 * 7, getHeight() / 8 * 3, 0, 90, false, mPaint);
}
第一个图形是 userCenter 为 true 的效果图;第二个图形则是 userCenter 为 false 的效果图。
四、小示例
学习了 Canvas 绘制图形的基本用法后,通过一个具体的例子来综合运用绘制图形方法。这个具体的例子是画圆饼,相信大家应该经常看到这种图形。这个例子是参考这篇博客:安卓自定义View进阶-Canvas之绘制图形,先看看自己实现的效果图吧。
简单分析下:画这类图最主要的元素是数据,即总共有多少个模块,每个模块的值具体是多少,每个模块在总数中所占的比例以及用某种颜色表示某个模块。因此,这个可以将其封装为数据类,即 Bean。数据有了之后,接下来就根据我们学过的知识点进行绘制,来看下具体代码吧:
用户数据类 Bean
public class Bean {
private int color;
private float percentage;
private String name;
private float angle;
private float value;
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public float getPercentage() {
return percentage;
}
public void setPercentage(float percentage) {
this.percentage = percentage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
}
自定义 View 绘制圆饼 RoundCakeView
public class RoundCakeView extends View {
private final static String TAG = RoundCakeView.class.getCanonicalName();
private Paint mPaint;
private List<Bean> mList;
private float mStartAngle;
private int mWidth;
private int mHeight;
public RoundCakeView(Context context) {
super(context);
}
public RoundCakeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public RoundCakeView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.GREEN);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.mWidth = w;
this.mHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw()");
int x = mWidth / 2;
int y = mHeight / 2;
int r = 200;
RectF rectF = new RectF(x - r, y - r, x + r, y + r);
float currentAngle = mStartAngle;
// draw circle point
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(5);
canvas.drawPoint(x, y, mPaint);
// draw circle
mPaint.setStrokeWidth(0);
mPaint.setColor(Color.RED);
canvas.drawCircle(x, y, r, mPaint);
mPaint.setStyle(Paint.Style.FILL);
// draw arc
for (int i = 0; i < mList.size(); i++) {
Bean bean = mList.get(i);
mPaint.setColor(bean.getColor());
canvas.drawArc(rectF, currentAngle, bean.getAngle(), true, mPaint);
currentAngle += bean.getAngle();
}
}
public void setList(List<Bean> list) {
this.mList = list;
invalidate();
}
public void setStartAngle(float angle) {
this.mStartAngle = angle;
}
}
那么接下来就是显示出效果了,先看下布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.pan.canvasdemo.MainActivity">
<com.pan.canvasdemo.RoundCakeView
android:id="@+id/rcv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private final static String TAG = MainActivity.class.getCanonicalName();
private RoundCakeView mView;
private int [] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.DKGRAY, Color.MAGENTA};
private int [] mValues = {23, 98, 122, 78, 225};
private List<Bean> mList = new ArrayList<>();
private float mSum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
initData();
mView.setStartAngle(0);
mView.setList(mList);
}
private void init() {
mView = (RoundCakeView) findViewById(R.id.rcv);
}
private void initData() {
for (int i = 0; i < mValues.length; i++) {
mSum += mValues[i];
}
for (int i = 0; i < 5; i++) {
Bean bean = new Bean();
bean.setColor(mColors[i]);
bean.setValue(mValues[i]);
bean.setPercentage(bean.getValue() / mSum);
bean.setAngle(bean.getPercentage() * 360);
bean.setName(i + "");
mList.add(bean);
Log.d(TAG, "Value : " + bean.getValue());
Log.d(TAG, "Percentage : " + bean.getPercentage());
Log.d(TAG, "Angle : " + bean.getAngle());
}
}
}
以下就是画圆饼的具体实现,逻辑不是很难懂,加上部分注释应该很容易理解。