在笔者的《 View绘制流程—自定义View相关》的文章中,讲解到,Android中最常见的自定义步骤:
①自定义View属性;
②在View的构造方法中获得自定义的属性;
③重写onMeasure(); --> 并不是必须的,大部分的时候还需要覆写
④重写onDraw();
以上四步刚好对应了笔者的《自定义View精炼详解第(一)课:基础理论部分和简单小实现》中的描述:最常见也最有效的自定义方式就是继承现有的控件和布局实现。我们在前面的课程中已经讲解了自定义View的自定义属性部分的讲解,接下来本节中笔者将带领大家一起学习onDraw()方法的相关内容,并在最后实现一个示例。
(PS:本节内容较多)
(1)Canvas类的定义和作用:《Android群英传》中对于Canvas的描述为:当测量好了一个View之后,我们就可以简单地重写onDraw()方法,并在Canvas对象上来绘制所需要的图形,首先我们来了解一下利用西永2D绘图API所必须的使用到的Canvas对象。要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制。Canvas就像是一个画板,使用Paint作为画笔就可以在上面作画了。
接着我们在看官方文档
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 (e.g. Rect,Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
由官方文档,我们知道,Canvas是onDraw()方法中的唯一参数,在绘图时需要明确四个核心的东西(basic components):
用什么工具画?
这个小问题很简单,我们需要用一支画笔(Paint)来绘图。
当然,我们可以选择不同颜色的笔,不同大小的笔。
把图画在哪里呢?
我们把图画在了Bitmap上,它保存了所绘图像的各个像素(pixel)。
也就是说Bitmap承载和呈现了画的各种图形。
画的内容?
根据自己的需求画圆,画直线,画路径。
怎么画?
调用canvas执行绘图操作。
比如,canvas.drawCircle(),canvas.drawLine(),canvas.drawPath()将我们需要的图像画出来。
(2)利用Canvas绘制基本图形
①创建画笔:要想绘制内容,首先需要先创建一个画笔,代码如下:
// 1.创建一个画笔
private Paint mPaint = new Paint();
// 2.初始化画笔
private void initPaint() {
mPaint.setColor(Color.BLACK); //设置画笔颜色
mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
mPaint.setStrokeWidth(10f); //设置画笔宽度为10px
}
// 3.在构造函数中初始化
public SloopView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
②绘制颜色:绘制颜色是填充整个画布,常用于绘制底色。
canvas.drawColor(Color.BLUE); //绘制蓝色
③绘制点:可以绘制一个点,也可以绘制一组点,代码如下:
canvas.drawPoint(200, 200, mPaint); //在坐标(200,200)位置绘制一个点
canvas.drawPoints(new float[]{ //绘制一组点,坐标位置由float数组指定
500,500,
500,600,
500,700
},mPaint);
④绘制直线:绘制直线需要两个点,初始点和结束点,同样绘制直线也可以绘制一条或者绘制一组:
canvas.drawLine(300,300,500,600,mPaint); // 在坐标(300,300)(500,600)之间绘制一条直线
canvas.drawLines(new float[]{ // 绘制一组线 每四数字(两个点的坐标)确定一条线
100,200,200,200,
100,300,200,300
},mPaint);
⑤绘制矩形:确定确定一个矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。
关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形进行绘制。 其余两种是先将矩形封装为Rect或RectF(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制,如下:
// 第一种
canvas.drawRect(100,100,800,400,mPaint);
// 第二种
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);
// 第三种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRect(rectF,mPaint);
⑥绘制圆角矩形:绘制圆角矩形也提供了两种重载方式,如下:
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);
// 第二种
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);
⑦绘制圆:绘制圆形也比较简单, 绘制圆形有四个参数,前两个是圆心坐标,第三个是半径,最后一个是画笔,如下:
canvas.drawCircle(500,500,400,mPaint); // 绘制一个圆心坐标在(500,500),半径为400 的圆。
RectF rectF = new RectF(100,100,600,600);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);
// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);
⑧绘制圆弧:绘制圆弧就比较神奇一点了,为了理解这个比较神奇的东西,我们先看一下它需要的几个参数:
RectF rectF2 = new RectF(100,700,600,1200);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);
// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);
————————————————————我是分隔线——————————————————————
⑨简要介绍Paint:看了上面这么多,相信有一部分人会产生一个疑问,如果我想绘制一个圆,只要边不要里面的颜色怎么办?很简单,绘制的基本形状由Canvas确定,但绘制出来的颜色,具体效果则由Paint确定。如果你注意到了的话,在一开始我们设置画笔样式的时候是这样的:mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充。为了展示方便,容易看出效果,之前使用的模式一直为填充模式,实际上画笔有三种模式,如下:
STROKE //描边
FILL //填充
FILL_AND_STROKE //描边加填充
为了区分三者效果我们做如下实验:
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40); //为了实验效果明显,特地设置描边宽度非常大
// 描边
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);
// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);
// 描边加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);
————————————————————我是分隔线——————————————————————
上效果图:
上完整代码:
public class PieView extends View {
// 颜色表
private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
0xFFE6B800, 0xFF7CFC00};
// 饼状图初始绘制角度
private float mStartAngle = 0;
// 数据
private ArrayList mData;
// 宽高
private int mWidth, mHeight;
// 画笔
private Paint mPaint = new Paint();
public PieView(Context context) {
this(context, null);
}
public PieView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (null == mData)
return;
float currentStartAngle = mStartAngle; // 当前起始角度
canvas.translate(mWidth / 2, mHeight / 2); // 将画布坐标原点移动到中心位置
float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8); // 饼状图半径
RectF rect = new RectF(-r, -r, r, r); // 饼状图绘制区域
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
mPaint.setColor(pie.getColor());
canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);
currentStartAngle += pie.getAngle();
}
}
// 设置起始角度
public void setStartAngle(int mStartAngle) {
this.mStartAngle = mStartAngle;
invalidate(); // 刷新
}
// 设置数据
public void setData(ArrayList mData) {
this.mData = mData;
initData(mData);
invalidate(); // 刷新
}
// 初始化数据
private void initData(ArrayList mData) {
if (null == mData || mData.size() == 0) // 数据有问题 直接返回
return;
float sumValue = 0;
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
sumValue += pie.getValue(); //计算数值和
int j = i % mColors.length; //设置颜色
pie.setColor(mColors[j]);
}
float sumAngle = 0;
for (int i = 0; i < mData.size(); i++) {
PieData pie = mData.get(i);
float percentage = pie.getValue() / sumValue; // 百分比
float angle = percentage * 360; // 对应的角度
pie.setPercentage(percentage); // 记录百分比
pie.setAngle(angle); // 记录角度大小
sumAngle += angle;
Log.i("angle", "" + pie.getAngle());
}
}
}
至此关于自定义View的第三个阶段draw先写这么多吧。其实,在该阶段涉及到的东西非常多,我们这提到的仅仅是九牛一毛,也只是一些常用的基础性的东西。笔者也期望自己以后有时间和精力和大家一道深入地学习draw部分的相关知识和开发技能。
最后奉上一张重要的Draw属性常用表,开发中可以参考:
操作类型 |
相关API |
备注 |
绘制颜色 |
drawColor, drawRGB, drawARGB |
使用单一颜色填充整个画布 |
绘制基本形状 |
drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc |
依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
绘制图片 |
drawBitmap, drawPicture |
绘制位图和图片 |
绘制文本 |
drawText, drawPosText, drawTextOnPath |
依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
绘制路径 |
drawPath |
绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
顶点操作 |
drawVertices, drawBitmapMesh |
通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
画布剪裁 |
clipPath, clipRect |
设置画布的显示区域 |
画布快照 |
save, restore, saveLayerXxx, restoreToCount, getSaveCount |
依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
画布变换 |
translate, scale, rotate, skew |
依次为 位移、缩放、 旋转、错切 |
Matrix(矩阵) |
getMatrix, setMatrix, concat |
实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |