自定义View之ondraw绘制
程序员文章站
2022-05-30 22:46:10
...
系统的复习了一下ondraw绘制的基础知识,还绘制了几个图形作为练习,这篇博客记录了下常用的绘制基础方法,和两个图形demo,一个是扇形图,一个是直方图。
自定义绘制最常用的方式是重写绘制方法onDraw(),ondraw()的关键是 Canvas 的使用
Canvas分为两方面。
1,drawXXX(),关键Path,Paint。
2,辅助方法,范围裁切和几何变换。
还可以使用不同的绘制方法来控制遮盖关系
下面代码直接演示drawXXX()常用方法和Path常用方法,,因为我是边写边运行测试,试看每个方法的效果,所以都依次注释了。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//在绘制的时候,往往需要开启抗锯齿来让图形和文字的边缘更加平滑
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);// 来动态开关抗锯齿
/**
* 三种模式FILL, STROKE 和 FILL_AND_STROKE 。它的默认值是 FILL,填充模式。
* FILL 是填充模式,STROKE 是画线模式(即勾边模式),FILL_AND_STROKE 是两种模式一并使用:既画线又填充。
*/
paint.setStyle(Paint.Style.FILL);//不设置默认是实心,绘制模式改为画线模式
paint.setStrokeWidth(1);
canvas.drawCircle(150, 150, 100, paint);//画圆
/**
* 重载方法 drawRect(RectF rect, Paint paint) 和 drawRect(Rect rect, Paint paint) ,
* 让你可以直接填写 RectF 或 Rect 对象来绘制矩形。
*/
canvas.drawRect(100, 100, 300, 400, paint);//画矩形
paint.setStrokeWidth(20);//点的大小
paint.setStrokeCap(Paint.Cap.ROUND);//点的形状 BUTT,SQUARE ROUND,前两参数是方的,后一个圆的
canvas.drawPoint(100, 100, paint);//画点
float[] points = {0, 0, 50, 50, 50, 100, 100, 50, 100, 100, 150, 50, 150, 100};
canvas.drawPoints(points, 2 /* 跳过两个数,即前两个 0 */, 8 /* 一共绘制 8 个数(4 个点)*/, paint); //批量花点
/**
* 重载方法 drawOval(RectF rect, Paint paint),让你可以直接填写 RectF 来绘制椭圆
*/
canvas.drawOval(100, 50, 500, 200, paint);//椭圆
canvas.drawLine(200, 200, 800, 500, paint);//画线 ,参数 分别是线的起点和终点坐标。
float[] points2 = {20, 20, 120, 20, 70, 20, 70, 120, 20, 120, 120, 120, 150, 20, 250, 20, 150, 20, 150, 120, 250, 20, 250, 120, 150, 120, 250, 120};
canvas.drawLines(points2, paint);//批量画直线
/**
* 重载方法 drawRoundRect(RectF rect, float rx, float ry, Paint paint),让你可以直接填写 RectF 来绘制圆角矩形。
*/
canvas.drawRoundRect(400, 50, 700, 200, 30, 30, paint);//画圆角矩形,四条边的坐标,rx 和 ry 是圆角的横向半径和纵向半径。
/**
* float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
* left, top, right, bottom 描述的是这个弧形所在的椭圆
* startAngle 是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度)
* sweepAngle 是弧形划过的角度
* useCenter 表示是否连接到圆心,如果不连接到圆心,就是弧形,如果连接到圆心,就是扇形
*/
canvas.drawArc(50, 0, 300, 250, 10, 260, true, paint);//绘制弧形或扇形
/**
* drawPath(Path path, Paint paint) 画自定义图形,这个方法有点复杂
* Path 可以描述直线、二次曲线、三次曲线、圆、椭圆、弧形、矩形、圆角矩形。把这些图形结合起来,就可以描述出很多复杂的图形。
*/
Path path = new Path(); // 初始化 Path 对象
// 使用 path 对图形进行描述(这段描述代码不必看懂)
path.addArc(200, 200, 400, 400, -225, 225);
path.arcTo(400, 200, 600, 400, -180, 225, false);
path.lineTo(400, 542);
canvas.drawPath(path, paint); // 绘制出 path 描述的图形(心形)
/**
* Path 有两类方法,一类是直接描述路径的,另一类是辅助的设置或计算。
* Path 方法第一类:直接描述路径,还可以细分为两组:添加子图形和画线(直线或曲线)
* 第一组: addXxx() ——添加子图形
*/
/**
*最后一个参数 dir 是画圆的路径的方向。
* 路径方向有两种:顺时针 (CW clockwise) 和逆时针 (CCW counter-clockwise) 。
* 对于普通情况,这个参数填 CW 还是填 CCW 没有影响。它只是在需要填充图形 (FILL 或 FILL_AND_STROKE)
* 并且图形出现自相交时,用于判断填充范围的。
*/
path.addCircle(300, 300, 200, Path.Direction.CCW);// 添加圆
path.addCircle(550, 300, 200, Path.Direction.CCW);// 添加圆
canvas.drawPath(path, paint);
//下面的方法跟上面类似
path.addOval( float left, float top, float right, float bottom, Direction dir);//添加椭圆
path.addOval(RectF oval, Direction dir);//添加椭圆
path.addRect( float left, float top, float right, float bottom, Direction dir);// 添加矩形
path.addRect(RectF rect, Direction dir);// 添加矩形
path.addRoundRect(RectF rect, float rx, float ry, Direction dir);// 添加圆角矩形
path.addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir);// 添加圆角矩形
path.addRoundRect(RectF rect, float[] radii, Direction dir);// 添加圆角矩形
path.addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir);// 添加圆角矩形
path.addPath(Path path) ;//添加另一个 Path
/**
*
* 第二组: xxxTo() ——画线(直线或曲线)
*
* 之前是添加的完整封闭图形(除了 addPath() ),而这一组添加的只是一条线。
*/
path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线
/**
* 相对当前位置的相对坐标 (前缀 r 指的就是 relatively 「相对地」)。
* 所谓当前位置,即最后一次调用画 Path 的方法的终点位置。初始值为原点 (0, 0)。
*/
path.rLineTo(100, 0); // 相对当前位置 (100, 100) 向正右方 100 像素的位置画一条直线
canvas.drawPath(path, paint);
/**
* 贝塞尔曲线:贝塞尔曲线是几何上的一种曲线。它通过起点、控制点和终点来描述一条曲线,主要用于计算机图形学。
* 概念总是说着容易听着难,总之使用它可以绘制很多圆润又好看的图形
*/
path.quadTo( float x1, float y1, float x2, float y2);//画二次贝塞尔曲线
path.rQuadTo( float dx1, float dy1, float dx2, float dy2);//相对当前位置 画二次贝塞尔曲线
//
path.cubicTo( float x1, float y1, float x2, float y2, float x3, float y3);//
path.rCubicTo( float x1, float y1, float x2, float y2, float x3, float y3);// 画三次贝塞尔曲线
//
path.lineTo(100, 100); // 画斜线
/**
* 移动到目标位置
* rMoveTo(float x, float y) 相对位置移动到目标位置
*/
path.moveTo(200, 100); //我移
path.lineTo(200, 0); // 画竖线
canvas.drawPath(path, paint);
/**
* 特殊画线方法
* 画弧形
* startAngle 是弧形的起始角度(x 轴的正向,即正右的方向,是 0 度的位置;顺时针为正角度,逆时针为负角度)
* sweepAngle 是弧形划过的角度
*/
path.arcTo(100, 100, 300, 300, -90, 90, true); // 强制移动到弧形起点(无痕迹)
/**
* 一个叫 arcTo ,一个叫 addArc(),都是弧形
* addArc() 只是一个直接使用了 forceMoveTo = true 的简化版 arcTo()
*/
path.addArc(100, 100, 300, 300, -90, 90);
/**
* 使用 close() 封闭子图形。等价于 path.lineTo(100, 100)
*/
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150);
path.close();
/**
* path 有两类方法,一类是直接描述路径的,另一类是辅助的设置或计算。
* Path 方法第二类:辅助的设置或计算
* 这类方法的使用场景比较少,我在这里就不多讲了,只讲其中一个方法: setFillType(FillType fillType)。
*
* 前面在说 dir 参数的时候提到, Path.setFillType(fillType) 是用来设置图形自相交时的填充算法的:
* 方法中填入不同的 FillType 值,就会有不同的填充效果。FillType 的取值有四个:
* EVEN_ODD
* WINDING (默认值)
* INVERSE_EVEN_ODD
* INVERSE_WINDING
*/
/**
* 绘制 Bitmap 对象,也就是把这个 Bitmap 中的像素内容贴过来
* 重载方法
* drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
* drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
* drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
*/
canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);//画 Bitmap
/**
* 绘制文字
* 界面里所有的显示内容,都是绘制出来的
*/
String text = "hello world";
paint.setTextSize(18);
canvas.drawText(text, 100, 25, paint);
paint.setTextSize(36);
canvas.drawText(text, 100, 70, paint);
paint.setTextSize(60);
canvas.drawText(text, 100, 145, paint);
paint.setTextSize(84);
canvas.drawText(text, 100, 240, paint);
}
}
下面是直方图的示例
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 绘制直方图
*/
//先画出背景色
canvas.drawColor(Color.BLACK);
//再画出XY轴
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(5);
canvas.drawLine(50, 0, 50, 300, paint);
canvas.drawLine(50, 300, 600, 300, paint);
//再画出直方
Paint paint2 = new Paint();
paint2.setColor(Color.YELLOW);
paint2.setStyle(Paint.Style.FILL);
canvas.drawRect(70, 270, 150, 300, paint2);
canvas.drawRect(170, 210, 250, 300, paint2);
canvas.drawRect(270, 100, 350, 300, paint2);
canvas.drawRect(370, 170, 450, 300, paint2);
canvas.drawRect(470, 230, 550, 300, paint2);
//再写上文字
Paint paint3 = new Paint();
paint3.setColor(Color.WHITE);
paint3.setTextSize(30);
canvas.drawText("AAA", 75, 335, paint3);
canvas.drawText("BBB", 175, 335, paint3);
canvas.drawText("CCC", 275, 335, paint3);
canvas.drawText("DDD", 375, 335, paint3);
canvas.drawText("FFF", 475, 335, paint3);
}
下面是扇形图示例
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 绘制饼图
*/
//画线+文字(第一块饼)
Paint paint4 = new Paint();
paint4.setColor(Color.BLACK);
paint4.setTextSize(45);
paint4.setStrokeWidth(5);
canvas.drawLine(30, 200, 200, 200, paint4);
canvas.drawLine(200, 200, 250, 250, paint4);
canvas.drawText("安卓份额", 10, 180, paint4);
//第一块饼
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawArc(150, 150, 750, 750, -60, -130, true, paint);
//第二块饼
Paint paint2 = new Paint();
paint2.setColor(Color.BLUE);
paint2.setStyle(Paint.Style.FILL);
canvas.drawArc(150, 160, 760, 760, -190, -100, true, paint2);
//第三块饼
Paint paint3 = new Paint();
paint3.setColor(Color.YELLOW);
paint3.setStyle(Paint.Style.FILL);
canvas.drawArc(160, 150, 770, 750, -290, -130, true, paint3);
}
下面代码直接演示Paint画笔的常用方法
* 颜色 * 效果 * drawText() 相关 * 初始化
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
/**
* -------------- Paint 画笔专题 -------------------
*
* Paint 的 API 大致可以分为 4 类:
* 颜色
* 效果
* drawText() 相关
* 初始化
*/
/**
* -- 颜色 --
*/
paint.setColor(Color.parseColor("#009688"));//直接设置颜色
paint.setARGB(100, 255, 0, 0); //直接ARGB
/**
* 设置 Shader 着色器
* 用它的几个子类
* LinearGradient 线性渐变
* RadialGradient 辐射渐变
* SweepGradient 扫描渐变
* BitmapShaderComposeShader 不是渐变,用 Bitmap 的像素来作为图形或文字的填充
*/
// Shader shader = new LinearGradient(100, 100, 500, 500, Color.parseColor("#E91E63"),
// Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);//CLAMP 会在端点之外延续端点处的颜色;MIRROR 是镜像模式;REPEAT 是重复模式
// Shader shader = new RadialGradient(300, 300, 200, Color.parseColor("#E91E63"),
// Color.parseColor("#2196F3"), Shader.TileMode.CLAMP);
// Shader shader = new SweepGradient(300, 300, Color.parseColor("#E91E63"),
// Color.parseColor("#2196F3"));
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
Shader shader1 = new BitmapShader(bitmap1, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// 第二个 Shader:从上到下的线性渐变(由透明到黑色)
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
Shader shader2 = new BitmapShader(bitmap2, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// ComposeShader:结合两个 Shader
Shader shader = new ComposeShader(shader1, shader2, PorterDuff.Mode.SRC_OVER);// PorterDuff.Mode 一共有 17 个,可以分为两类:Alpha 合成 (Alpha Compositing),混合 (Blending)
paint.setShader(shader);
paint.setShader(shader2);
canvas.drawCircle(300, 300, 300, paint);
/**
* ColorFilter 这个类,它的名字已经足够解释它的作用:为绘制设置颜色过滤。就是为绘制的内容设置一个统一的过滤策略,
* 然后 Canvas.drawXXX() 方法会对每个像素都进行过滤后再绘制出来。
* ColorFilter 并不直接使用,而是使用它的子类。
* 它共有三个子类:LightingColorFilter PorterDuffColorFilter 和 ColorMatrixColorFilter。
* LightingColorFilter 是用来模拟简单的光照效果的。
* PorterDuffColorFilter 的作用是使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。
* ColorMatrixColorFilter 使用一个 ColorMatrix 来对颜色进行处理。 ColorMatrix 这个类,内部是一个 4x5 的矩阵
*/
ColorFilter lightingColorFilter = new LightingColorFilter(0x00ffff, 0x000000);
paint.setColorFilter(lightingColorFilter);
/**
* 二,效果效果类的 API ,指的就是抗锯齿、填充/轮廓、线条宽度等等这些。
*/
paint.setAntiAlias(true); //设置抗锯齿
paint.setStyle(Paint.Style.FILL); //设置线条风格还是填充风格
/**
* 线条形状
* setStrokeWidth(float width),
* setStrokeCap(Paint.Cap cap)
* setStrokeJoin(Paint.Join join)
* setStrokeMiter(float miter)
*/
paint.setStrokeCap(Paint.Cap.ROUND);//设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT。
paint.setStrokeJoin(Paint.Join.MITER);//设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER。
paint.setStrokeMiter(2);//这个方法是对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。所谓「延长线的最大值」,是这么一回事:
/**
* 色彩优化
* setDither(boolean dither) 和 setFilterBitmap(boolean filter)
* 它们的作用都是让画面颜色变得更加「顺眼」,但原理和使用场景是不同的。
*/
paint.setDither(true);//设置图像的抖动
paint.setFilterBitmap(true);//设置是否使用双线性过滤来绘制 Bitmap
/**
* 使用 PathEffect 来给图形的轮廓设置效果。对 Canvas 所有的图形绘制有效,
* 也就是 drawLine() drawCircle() drawPath() 这些方法
* 有6 种 PathEffect。PathEffect 分为两类,
* 单一效果的 CornerPathEffect DiscretePathEffect DashPathEffect PathDashPathEffect
* 和组合效果的 SumPathEffect ComposePathEffect。
*/
PathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 10);//虚线
PathEffect pathEffect2 = new CornerPathEffect(20);//把所有拐角变成圆角。
PathEffect pathEffect3 = new DiscretePathEffect(20, 10);//把线条进行随机的偏离,让轮廓变得乱七八糟。乱七八糟的方式和程度由参数决定。
//这个方法比 DashPathEffect 多一个前缀 Path ,所以顾名思义,它是使用一个 Path 来绘制「虚线」
Path dashPath = ...; // 使用一个三角形来做 dash
PathEffect pathEffect4 = new PathDashPathEffect(dashPath, 40, 0, PathDashPathEffect.Style.TRANSLATE);//TRANSLATE:位移 ROTATE:旋转 MORPH:变体
// 组合效果类.分别按照两种 PathEffect分别对目标进行绘制。
PathEffect dashEffect = new DashPathEffect(new float[]{20, 10}, 0);
PathEffect discreteEffect = new DiscretePathEffect(20, 5);
pathEffect = new SumPathEffect(dashEffect, discreteEffect);
//组合效果类的 PathEffect 。不过它是先对目标 Path 使用一个 PathEffect,然后再对这个改变后的 Path 使用另一个 PathEffect。
PathEffect dashEffect5 = new DashPathEffect(new float[]{20, 10}, 0);
PathEffect discreteEffect6 = new DiscretePathEffect(20, 5);
pathEffect = new ComposePathEffect(dashEffect, discreteEffect);
paint.setPathEffect(pathEffect);
canvas.drawCircle(300, 300, 200, paint);
/**
* setShadowLayer(float radius, float dx, float dy, int shadowColor)
* 在之后的绘制内容下面加一层阴影。
* 如果要清除阴影层,使用 clearShadowLayer()
* 在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。
*/
paint.setShadowLayer(10, 0, 0, Color.RED);
/**
* 模糊效果的 MaskFilter 有两种BlurMaskFilter EmbossMaskFilter
* NORMAL: 内外都模糊绘制
* SOLID: 内部正常绘制,外部模糊
* INNER: 内部模糊,外部不绘制
* OUTER: 内部不绘制,外部模糊
*/
paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.NORMAL));
int[] dd = new int[]{12, 23};
/**
* EmbossMaskFilter
* direction 是一个 3 个元素的数组,指定了光源的方向; ambient 是环境光的强度,数值范围是 0 到 1; specular 是炫光的系数; blurRadius 是应用光线的范围。
*/
paint.setMaskFilter(new EmbossMaskFilter(new float[]{12, 23}, 20, 20, 20));
/**
* Paint 有些设置是文字绘制相关的,即和 drawText() 相关的
* 文字大小
* 文字间隔
* 文字效果
*/
/**
* 初始化类
* 它们是用来初始化 Paint 对象,或者是批量设置 Paint 的多个属性的方法。
*/
/**
* reset()
* 重置 Paint 的所有属性为默认值。相当于重新 new 一个,不过性能当然高一些
*/
/**
* set(Paint src)
* 把 src 的所有属性全部复制过来。相当于调用 src 所有的 get 方法,然后调用这个 Paint的对应的 set 方法来设置它们。
*/
}
参考文章 https://zhuanlan.zhihu.com/p/27787919
https://zhuanlan.zhihu.com/p/27919855
上一篇: 自定义Background
推荐阅读
-
自定义View之kotlin绘制折线图实例教程
-
详谈自定义View之GridView单选 金额选择Layout-ChooseMoneyLayout
-
Android自定义View绘制四位数随机码
-
Android自定义View绘制的方法及过程(二)
-
android自定义view之模拟qq消息拖拽删除效果
-
Android开发使用自定义View将圆角矩形绘制在Canvas上的方法
-
Android自定义View实现绘制虚线的方法详解
-
Android自定义view Path 的高级用法之搜索按钮动画
-
Android进阶之绘制-自定义View完全掌握(二)
-
Android进阶之绘制-自定义View完全掌握(一)