新手学自定义View系列(一)之canvas绘制API
简述:
- Android中自定义View开发的遇到的问题?
Android开发中自定义View可以说是常见的技术,不管是新手还是老司机都非常了解。但是目前Android自定义View的现状是很多人对自定义View都能说得出来一些,但是实际上根据需求开发起来却比较难。老实说这也是我之前的想法,我也一直在思考这个原因,个人觉得原因是:
第一 对自定义View中的API不熟悉;
第二 对自定义View没有一个系统认识和深入了解;
第三 对掌握的自定义View的掌握没有一个分类管理思想;
-
canvas绘制的API的细节深入的学习能解决什么问题?
此篇博客canvas的API的学习可以系统掌握canvas.drawxxx()系列的方法
以及绘制原理。 -
本系列博客能给你带来什么?
个人认为Android中的自定义View API的学习主要分为几个方面:
第一 Canvas绘制系列的API的学习;
第二 Paint 画笔系列的API的学习;
第三 辅助绘制(clipxxx系列方法)canvas的几何变换(位移、旋转、错切)API的学习;
第四 自定义View的测量、绘制(onMeasure、onSizeChange、onDraw);
第五 自定义ViewGroup的测量、布局、绘制(onMeasure、onSizeChange、onLayout、onDraw);
第六 事件分发、事件拦截、滑动冲突以及触摸反馈的接口的回调设计;
1、canvas绘制颜色
一般用于在绘制之前设置底色,或者在绘制之后为界面设置半透明蒙版
canvas中绘制颜色主要几种方法:
- canvas.drawColor(int color)
- canvas.drawRGB(int r, int g, int b)
- canvas.drawARGB(int a, int r, int g, int b)
canvas.drawColor(Color.parseColor("#00bfa5"));//int color
canvas.drawRGB(100, 200, 100);//设置red值(0~255),green值(0~255),blue值(0~255)
canvas.drawARGB(100, 100, 200, 100);//设置alpha值(0~255),设置red值(0~255),green值(0~255),blue值(0~255)

2、canvas绘制形状
- 绘制圆形:
- canvas.drawCircle(float dx, float dy, float radius, Paint paint)
mPaint.setStyle(Paint.Style.FILL);//设置填充style为FILL
mPaint.setStyle(Paint.Style.STROKE);//设置填充style为STROKE
canvas.drawCircle(mWidth / 2, mHeight / 2, 200, mPaint);//(dx:圆心横坐标 dy:圆心纵坐标 radius:半径 paint 画笔)


-
绘制矩形:
canvas中绘制矩形主要几种方法:
Rect和RectF的细微区别是Rect的绘制的单位类型是int,而RectF的绘制单位类型是float - canvas.drawRect(int left, int top, int right, int bottom, Paint paint);
- canvas.drawRect(Rect rect, Paint paint);
- canvas.drawRect(RectF rectF, Paint paint);
mPaint.setStyle(Paint.Style.FILL);//设置填充style为FILL
mPaint.setStyle(Paint.Style.STROKE);//设置填充style为STROKE
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRect(mWidth / 2 - 200, 200, mWidth / 2 + 200, 600, mPaint);//left:矩形的左边离Y轴的距离;top:矩形的顶边离X轴的距离;right的右边离Y轴的距离;bottom:矩形的底部边离X轴的距离


-
绘制圆角矩形:
canvas绘制圆角矩形主要有两个方法:
但是这两个方法是等价的,绘制出来的效果都是一样的。 - drawRoundRect(RectF rect, float rx, float ry, Paint paint)
- drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
int rx = 80;//圆角矩形的圆角所处的椭圆的横向半径
int ry = 40;//圆角矩形的圆角所处的椭圆的纵向半径
int left = mWidth / 2 - 200;//left:矩形的左边离Y轴的距离;
int top = 200;//top:矩形的顶边离X轴的距离;
int right = mWidth / 2 + 200;//right的右边离Y轴的距离;
int bottom = 400;//bottom:矩形的底部边离X轴的距离
mPaint.setStyle(Paint.Style.FILL);//设置填充style为FILL
mPaint.setStyle(Paint.Style.STROKE);//设置填充style为STROKE
mPaint.setStrokeWidth(5f);//设置stroke线形的宽度
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRoundRect(left, top, right, bottom, rx, ry, mPaint);//绘制圆角矩形


提出问题:圆角矩形绘制的原理是怎样的?为什么会有rx,ry两个半径?
圆角矩形的四个圆角实际上对应着四个椭圆弧的一部分,注意是椭圆弧不是正圆弧。那么rx也就是椭圆弧的横向半径,ry是椭圆弧的纵向半径,正好对应着椭圆的短半径和长半径。

(图片来源于GcsSloop大神,地址:http://ww3.sinaimg.cn/large/005Xtdi2jw1f2748fjw2bj308c0dwmx8.jpg)
结论:
当rx等于ry时,此时的四个椭圆弧也就变成四个正圆弧,那么每个圆角即为正圆弧的1/4;
当rx等于矩形宽度的一半,ry等于矩形高度的一半时,此时四个圆角所处的椭圆的重合,正好为矩形的内接椭圆;
当rx大于矩形宽度的一半,ry大于矩形高度的一半时,此时四个圆角所处椭圆实际上是无法计算出圆弧的,所以drawRoundRect对大于该数值的参数进行了修改,如果大于rx大于矩形宽度一半,ry大于矩形高度一半的参数均按照一半来处理。
结论论证:
可以针对圆角矩形绘制四个半径rx,ry椭圆来验证,若椭圆中有一部分圆弧与之重合,正好就证实我们的原理。
int rx = 80;//圆角矩形的圆角所处的椭圆的横向半径
int ry = 40;//圆角矩形的圆角所处的椭圆的纵向半径
int left = mWidth / 2 - 200;//left:矩形的左边离Y轴的距离;
int top = 200;//top:矩形的顶边离X轴的距离;
int right = mWidth / 2 + 200;//right的右边离Y轴的距离;
int bottom = 400;//bottom:矩形的底部边离X轴的距离
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRoundRect(left, top, right, bottom, rx, ry, mPaint);//绘制圆角矩形
canvas.drawOval(left, top, left + 2 * rx, top + 2 * ry, mPaint);//绘制左上角圆角的所处的椭圆
canvas.drawOval(right - 2 * rx, top, right, top + 2 * ry, mPaint);//绘制右上角圆角的所处的椭圆
canvas.drawOval(left, bottom - 2 * ry, left + 2 * rx, bottom, mPaint);//绘制左下角圆角的所处的椭圆
canvas.drawOval(right - 2 * rx, bottom - 2 * ry, right, bottom, mPaint);//绘制右下角圆角的所处的椭圆

当rx等于ry时,此时的四个椭圆弧也就变成四个正圆弧,那么每个圆角即为正圆弧的1/4;

当rx接近矩形宽度的一半,ry接近矩形高度的一半时,此时四个圆角所处的椭圆接近重合
int rx = 195;//矩形宽度的一半为200
int ry = 95;//矩形高度的一半为100
int left = mWidth / 2 - 200;//left:矩形的左边离Y轴的距离;
int top = 200;//top:矩形的顶边离X轴的距离;
int right = mWidth / 2 + 200;//right的右边离Y轴的距离;
int bottom = 400;//bottom:矩形的底部边离X轴的距离
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRoundRect(left, top, right, bottom, rx, ry, mPaint);//绘制圆角矩形
canvas.drawOval(left, top, left + 2 * rx, top + 2 * ry, mPaint);//绘制左上角圆角的所处的椭圆
canvas.drawOval(right - 2 * rx, top, right, top + 2 * ry, mPaint);//绘制右上角圆角的所处的椭圆
canvas.drawOval(left, bottom - 2 * ry, left + 2 * rx, bottom, mPaint);//绘制左下角圆角的所处的椭圆
canvas.drawOval(right - 2 * rx, bottom - 2 * ry, right, bottom, mPaint);//绘制右下角圆角的所处的椭圆

当rx等于矩形宽度的一半,ry等于矩形高度的一半时,此时四个圆角所处的椭圆的重合,正好为矩形的内接椭圆;

当rx大于矩形宽度的一半,ry大于矩形高度的一半时,此时四个圆角所处椭圆实际上是无法计算出圆弧的,所以drawRoundRect对大于该数值的参数进行了修改,如果大于rx大于矩形宽度一半,ry大于矩形高度一半的参数均按照一半来处理。(为了便于区别把椭圆的颜色绘制为grey)

-
绘制点:
canvas绘制点主要有几种方法: - canvas.drawPoint(float x, float y, Paint paint)//绘制单个点
- canvas.drawPoints(float[] pts, int offset, int count, Paint paint)//有选择性绘制多个点
- canvas.drawPoints(float[] pts, Paint paint)//绘制多个点
mPaint.setColor(Color.parseColor("#dd2c00"));
mPaint.setStrokeWidth(80f);
mPaint.setStrokeCap(Paint.Cap.ROUND);//绘制点Point,canvas.drawPoint默认绘制方形点,设置画笔的setStrokeCap可以绘制圆点
canvas.drawPoint(mWidth / 2, mHeight / 2, mPaint);
//绘制多个点:drawPoints(float[] opts, Paint paint)
//opts 点的一对(x,y)坐标的数组。(相邻的两个数分为一个点的x,y)
mPaint.setColor(Color.parseColor("#304ffe"));
float[] points = {mWidth / 2 - 200, 200, mWidth / 2 + 200, 200, mWidth / 2 - 200, 600, mWidth / 2 + 200, 600};
canvas.drawPoints(points, mPaint);
//有选择绘制多个点: drawPoints(float[] opts, offset, count, Paint paint)
//opts 点的一对(x,y)坐标的数组。(相邻的两个数分为一个点的x,y)
//offset:是相对于对points数组的元素下标做偏移
//count: 设置points数组从offset偏移算起元素的个数
//经过offset和count操作产生新的点的数组
mPaint.setColor(Color.RED);
canvas.drawPoints(points, 4, 4, mPaint);//在前两个点后画两个点

-
绘制椭圆:
canvas绘制椭圆主要有两种方法:
但是这两个方法是等价的,绘制出来的效果都是一样的。 - drawOval(RectF oval, Paint paint)
- drawOval(float left, float top, float right, float bottom, Paint paint)
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.parseColor("#FF9800"));
canvas.drawOval(mWidth / 2 - 400, 200, mWidth / 2 + 400, 600, mPaint);//外接矩形的left,top,right,bottom

mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.parseColor("#0091EA"));
RectF rectF = new RectF(mWidth / 2 - 200, 0, mWidth / 2 + 200, 800);//外接矩形的left,top,right,bottom
canvas.drawOval(rectF, mPaint);

-
绘制线段:
canvas绘制线段主要有几种方法: - drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
- drawLines(float[] pts, int offset, int count, Paint paint)
- drawLines(float[] pts, Paint paint)
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.GREEN);
canvas.drawLine(mWidth / 2 - 200, 200, mWidth / 2 + 200, 600, mPaint);//startX线的起点X坐标,startY线的起点Y坐标,endX线的终点X坐标,endY线的终点Y坐标

float[] points2 = {
mWidth / 2 - 200, 200,
mWidth / 2 + 200, 600,
mWidth / 2 + 200, 200,
mWidth / 2 - 200, 600,
mWidth / 2, 200,
mWidth / 2, 600,
mWidth / 2 - 200, 400,
mWidth / 2 + 200, 400
};//points2 点的一对(startX,startY,endX,endY)坐标的数组。(相邻的四个数为一条线的起点和终点的x,y坐标)
canvas.drawLines(points2, mPaint);//绘制多条线段

float[] points2 = {
mWidth / 2 - 200, 200,
mWidth / 2 + 200, 600,
mWidth / 2 + 200, 200,
mWidth / 2 - 200, 600,
mWidth / 2, 200,
mWidth / 2, 600,
mWidth / 2 - 200, 400,
mWidth / 2 + 200, 400
};//points2 点的一对(startX,startY,endX,endY)坐标的数组。(相邻的四个数为一条线的起点和终点的x,y坐标)
//offset:原理和drawPonits一样,是相对于对points2数组的元素下标做偏移
//count: 设置points2数组从offset偏移算起元素的个数
//经过offset和count产生新的点的数组
canvas.drawLines(points2, 4, 8, mPaint);

-
绘制弧形或者扇形(是针对所在椭圆来绘制弧形和扇形):
canvas绘制弧形或者扇形(是针对所在椭圆来绘制弧形和扇形)主要有两种方法:但是这两个方法是等价的,绘制出来的效果都是一样的。 - 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)
//canvas.drawArc(left,top,right,bottom,startAngle,sweepAngle,userCenter,paint)
//left,top,right,bottom: 固定出椭圆
//startAngle起始角度(0度为圆心横坐标向左,正数为顺时针,负数为逆时针)
//sweepAngle扫过的角度
//userCenter是否连接圆心(true:连接圆心,画扇形;false:不连接圆心,画弧形)
mPaint.setStyle(Paint.Style.STROKE);//设置椭圆填充style为STROKE
RectF rectF1 = new RectF(mWidth / 2 - 400, 200, mWidth / 2 + 400, 600);
mPaint.setStyle(Paint.Style.FILL);//设置扇形填充style为FILL
mPaint.setStyle(Paint.Style.STROKE);//设置扇形填充style为STROKE
canvas.drawOval(rectF1,mPaint);//绘制扇形所处的椭圆
canvas.drawArc(rectF1, 0, 120, true, mPaint);//扇形


//canvas.drawArc(left,top,right,bottom,startAngle,sweepAngle,userCenter,paint)
//left,top,right,bottom: 固定出椭圆
//startAngle起始角度(0度为圆心横坐标向左,正数为顺时针,负数为逆时针)
//sweepAngle扫过的角度
//userCenter是否连接圆心(true:连接圆心,画扇形;false:不连接圆心,画弧形)
mPaint.setStyle(Paint.Style.STROKE);//设置椭圆填充style为STROKE
RectF rectF1 = new RectF(mWidth / 2 - 400, 200, mWidth / 2 + 400, 600);
mPaint.setStyle(Paint.Style.FILL);//设置扇形填充style为FILL
mPaint.setStyle(Paint.Style.STROKE);//设置弧形填充style为STROKE
canvas.drawOval(rectF1,mPaint);//绘制弧形所处的椭圆(注意:绘制STROKE的弧形的时候不要绘制椭圆,否则会重合不便于观察结果)
canvas.drawArc(rectF1, 0, 120, false, mPaint);//弧形


-
绘制图片Bitmap:
canvas图片Bitmap主要有几种方法: - drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
canvas.translate(mWidth / 2, mHeight / 2);//移动坐标原点到View组件的正中间位置,这个操作会很方便我们绘制时算坐标。(涉及到canvas的几何变换后期会说到)
float[] points3 = {-mWidth / 2, 0, mWidth / 2, 0, 0, -mHeight / 2, 0, mHeight / 2};
canvas.drawLines(points3, mPaint);//绘制出坐标系线
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
canvas.drawBitmap(bitmap, -bitmap.getWidth() / 2, -bitmap.getHeight() / 2, mPaint);//图片左上角x,y坐标对应left,top

-
绘制文字:
canvas中涉及到文本的绘制的细节有很多,后期会专门说下关于文本的绘制,这里先了解下。 - canvas.drawText(char[] text, int index, int count, float x, float y, Paint paint)
- canvas.drawText(String text, float x, float y, Paint paint)
- canvas.drawText(String text, int start, int end, float x, float y, Paint paint)
- canvas.drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
canvas.translate(mWidth / 2, mHeight / 2);//移动坐标原点到View组件的正中间位置
mPaint.setTextSize(50);
mPaint.setColor(Color.GREEN);
mPaint.setFakeBoldText(true);
String text = "今天好像是情人节,单身狗还是撸撸代码吧";
mPaint.getTextBounds(text, 0, text.length(), mTextBound);//通过传入mTextBound(Rect对象),将文字的尺寸固定住,作用相当于测量文本尺寸。
canvas.drawText(text, -mTextBound.width() / 2, mTextBound.height() / 2, mPaint);//x,y指的是文本绘制起点坐标,注意文本的绘制起点是第一个字左下角还要向外偏移一点,至于为什么是这样后期会说到.

-
绘制Path类型自定义图型:
Path的知识在Canvas中绘制很重要,合理使用Path可以做出很多炫酷的组件以及提高绘制的效率。所以这里先给出个例子,下一期博客将会深入讲解canvas绘制中的Path。情人节了,这张图或许适合你,哈哈哈
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
mPath.arcTo(200, 200, 400, 400, -225, 225, true);
mPath.arcTo(400, 200, 600, 400, -180, 225, false);
mPath.lineTo(400, 542);
canvas.drawPath(mPath, mPaint);

结束:
到这里,这期博客就结束了,该博客旨在新手学习沉淀。感谢GcsSloop和扔物线大神对自定义View的博客的无私奉献。本系列博客均为自己学大神们自定义View总结。
小例子:本来是扔物线大神第一期的直方图的例子,在其基础上扩充几点:第一就是点击每个直方图会有监听回调的触摸返回,并把当前点击的直方图数据信息显示出来;第二就是在直方图基础上绘制了折线图。
抛出一个思考的问题:对于一个自定义View如何给某个绘制区域设置监听的点击事件触摸回调?



例子源码下篇博客给出。