欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义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);


    }
}

下面是直方图的示例

自定义View之ondraw绘制

@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);

    }

下面是扇形图示例

自定义View之ondraw绘制

@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