自定义 View 简单图形的绘制

Android 的绘制是一个庞大的工程,我了解的也不全,这里就把我学习的路线写一下。基本是按照 扔物线 的自定义 View 课程来学习的,他的网站:http://hencoder.com

我们通过往布局当中添加一个 TextView 控件来添加一行文字,添加一个 ImageView 来添加一张图片,实际上,这些文字、图片等等都是通过绘制展现在我们面前的。

如果我们需要绘制自己的组件,就需要去让我们的类去重写 onDraw 方法,所有的绘制操作都是在该方法中进行的。

onDraw 方法的参数是一个 Canvas,翻译过来是“画布”,实际上,Canvas 的功能却不单单是画布这么简单,我们的所有内容都是 Canvas “画”出来的,所以 Canvas 不单单是一个画布的功能。

Android 还有一个重要的对象:Paint,翻译过来是“颜料”,实际上它的功能是为我们“作画的画笔”提供一系列的设置操作,譬如颜色、风格等等。

Canvas 提供了一系列的 “drawXXX” 方法来帮助我们去画出我们需要的内容,点、线、圆(椭圆)、矩形、文字、图片等等。

Paint

在讲解图形绘制之前,先将一个重要的参数,几乎每一个 drawXXX 方法都有一个 paint 参数,该参数定义了画笔的一系列属性:实心空心、笔触宽度、画笔颜色、是否消除锯齿等等。

在这里把最常用的设置列一下,没有将到的后面遇到了再提。

  • setARGB:设置 RGB 颜色,第一个参数为透明度,后面三个参数为 RED、GREEN、BLUE 颜色。取值范围 0~255。
  • setColor:设置颜色。
  • setAlpha:设置透明度。取值范围 0~255。
  • setAntiAlias:设置是否开启抗锯齿。开启之后,系统会稍微改变图形边缘的颜色,让边界显得更圆润一些。
  • setStyle:设置画笔的风格,主要有三种:

    • Paint.Style.FILL:填充内部
    • Paint.Style.STROKE:描边
    • Paint.Style.FILL_AND_STROKE:填充内部和描边
  • setStrokeCap:设置笔帽风格。有以下几种风格可选:

    • Paint.Cap.BUTT:不设置笔帽
    • Paint.Cap.ROUND:圆头笔帽
    • Paint.Cap.SQUARE:方头笔帽
  • setStrokeWidth:设置笔触的宽度。
  • setShadowLayer:设置阴影。第一个参数为阴影角度,第二和第三为阴影在 X 轴和 Y 轴上的偏移量,第四个参数为阴影颜色。

常用的方法就是这几个,但是还有一些更加高级的用法,在后面会讲到。

最基础的绘制

绘制颜色

Canvas 的 setColor 和 setARGB 都是用来绘制颜色的:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //两种效果颜色是一样的
        canvas.drawColor(Color.parseColor("#FFF68F"));
        canvas.drawARGB(255, 255, 246, 143);
    }

这里的绘制颜色和 Paint 的 setColor 是不一样是,这个是绘制整个画布的颜色。

绘制点

在 Android 中有两个方法可以画点:

  • drawPoint(float x, float y, Paint paint):画一个点。参数说明:
    • float x:点的 X 轴坐标。
    • float y:点的 Y 轴坐标。
    • Paint paint:画笔。
  • public void drawPoints(float[] pts, Paint paint):画一组点。参数说明:
    • float[] pts,点的坐标数组,每个点由 X、Y 轴两个坐标值组成,格式为 [x0,y0, x1, y1, x2 ,y2 …]
    • Paint paint:画笔。
  • public void drawPoints(float[] pts, int offset, int count, Paint paint):画一组点。参数说明:
    • float[] pts,点的坐标数组,每个点由 X、Y 轴两个坐标值组成,格式为 [x0,y0, x1, y1, x2 ,y2 …]
    • int offset:在坐标数组中取值的偏移量。
    • int count:坐标数组中取值个数
    • Paint paint:画笔。

使用 drawPoint 方法需要设置画笔宽度,否则效果不明显。

示例:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setStrokeWidth(50);
        paint.setColor(Color.RED);

        float[] points = new float[]{100,100,200,200,300,300,400,400};
        canvas.drawPoints(points,paint);

        paint.setColor(Color.BLUE);
        canvas.drawPoints(points,1,3,paint);
    }

效果:

可以看到,蓝色点因为取了三个值,只能构成一个点,所以只画出一个。

绘制直线

连接两点,形成直线。

  • drawLine(float startX, float startY, float stopX, float stopY, Paint paint):画一条直线。参数说明:
    • float startX:起始点的 X 轴坐标。
    • float startY:起始点的 Y 轴坐标。
    • float stopX:结束点的 X 轴坐标。
    • float stopY:结束点的 Y 轴坐标。
    • Paint paint:画笔。
  • drawLines(float[] pts, int offset, int count, Paint paint):画一组直线。参数和画点类似,只不过是四个值一组。
  • drawLines(float[] pts, Paint paint):画一组直线。参数和画点类似,只不过是四个值一组。

绘制矩形

可以调用 Canvas 对象的 drawRect 来画一个矩形,也可以调用 drawRoungdRect 来画一个圆角矩形。

  • drawRect(float left, float top, float right, float bottom, Paint paint):画一个矩形,参数分别为:
    • float left:矩形左侧的 X 轴坐标,正常情况下可以理解为矩形左边距离左边界的距离。
    • float top:矩形顶部的 Y 轴坐标,正常情况下可以理解为矩形上边距离顶部的距离。
    • float right:矩形右侧 X 轴坐标,正常情况下可以理解为矩形右边距离左边界的距离。
    • float bottom:矩形底部 Y 轴坐标,正常情况下可以理解为矩形底部距离顶部的距离。
    • Paint paint:画笔
  • drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint):画一个圆角矩形,前四个参数和最后一个参数和 drawRect 相同,其他两个参数:
    • float rx:圆角 X 轴的半径。
    • float ry:圆角 Y 轴的半径。
  • drawRect(RectF rect, Paint paint):通过 RectF 对象创建一个矩形。
  • drawRect(Rect r, Paint paint):通过 Rect 对象创建一个矩形。
  • drawRoundRect(RectF rect, float rx, float ry,Paint paint):通过 RectF 创建一个圆角矩形。

说一下 Rect 和 RectF,通俗一点讲就是,Rect 封装了一个矩形四个边的 X/Y 轴数据,RectF 和 Rect 的不同点就是四个边的距离可以是 float...

再说圆角的半径,如果 X/Y 轴半径相同,则是一个规则的圆(角),X 轴数据和 Y 轴不同,就是一个椭圆(角),X>Y 是一个横着的椭圆,Y>X 是一个竖起来的椭圆。

示例:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setStrokeWidth(10);
        paint.setColor(Color.RED);

        canvas.drawRect(100, 100, 500, 500, paint);//画正方形

        paint.setColor(Color.GREEN);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(new Rect(600, 100, 900, 1000), paint);//画空心长方形

        paint.setColor(Color.BLUE);
        canvas.drawRoundRect(100, 700, 500, 1000, 20, 20, paint);//画圆角空心长方形
    }

效果:

绘制画和椭圆

调用 Canvas 的 drawCircle 来画一个圆,drawOval 来画一个椭圆。

  • drawCircle(float cx, float cy, float radius, Paint paint):画圆,参数说明:

    • float cx:圆心 X 轴坐标。
    • float cy:圆心 Y 轴坐标。
    • float radius:圆的半径。
    • Paint paint:画笔。
  • drawOval(RectF oval, Paint paint):通过 RectF 画一个椭圆。

  • drawOval(float left, float top, float right, float bottom, Paint paint):画一个椭圆,参数和矩形类似,椭圆上下左右四个端点的 X/Y 轴坐标。

示例:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setStrokeWidth(10);
        paint.setColor(Color.RED);

        canvas.drawCircle(500, 500, 200, paint);

        paint.setStyle(Paint.Style.STROKE);
        canvas.drawOval(100, 800, 900, 900, paint);
    }

效果如下:

绘制弧线和扇形

调用 Canvas 对象的 drawArc 方法可以画出圆弧。

如果 Paint 对象的 Style 为 FILL 或者 FILL_AND_STROKE,则效果为扇形

  • drawArc(float left, float top, float right, float bottom, float startAngle,
    float sweepAngle, boolean useCenter, Paint paint)
  • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
    Paint paint)

参数说明:

  • float startAngle:圆弧的起始角度。
  • float sweepAngle:圆弧旋转的角度。
  • boolean useCenter:是否使用圆心。

示例:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setStrokeWidth(5);//画笔笔触宽度
        paint.setColor(Color.RED);//画笔颜色
        paint.setStyle(Paint.Style.STROKE);//空心
        //为演示圆弧和 RectF 的关系,先画几个矩形。
        RectF rect1 = new RectF(100, 100, 400, 400);
        RectF rect2 = new RectF(100, 500, 400, 800);
        RectF rect3 = new RectF(100, 900, 400, 1200);
        RectF rect4 = new RectF(100, 1300, 400, 1600);
        canvas.drawRect(rect1, paint);//为展示方便,画一个矩形
        canvas.drawRect(rect2, paint);//为展示方便,画一个矩形
        canvas.drawRect(rect3, paint);//为展示方便,画一个矩形
        canvas.drawRect(rect4, paint);//为展示方便,画一个矩形

        paint.setColor(Color.GREEN);//将画笔颜色改为绿色,开始画圆弧

        //第一个圆弧,起始角度90°,旋转角度90°,使用圆心,并且不填充
        paint.setStyle(Paint.Style.STROKE); //不填充
        canvas.drawArc(rect1, 90, 90, true, paint);//使用圆心

        //第二个圆弧,起始角度90°,旋转角度90°,不使用圆心,空心
        canvas.drawArc(rect2, 90, 90, false, paint);//不使用圆心

        //第三个圆弧,起始角度180°,旋转角度120°,使用圆心,填充(扇形)
        paint.setStyle(Paint.Style.FILL_AND_STROKE);//实心
        canvas.drawArc(rect3, 180, 120, true, paint);//使用圆心

        //第四个圆弧,起始角度 180°,120°,不使用圆心,填充(扇形)
        canvas.drawArc(rect4, 180, 120, false, paint);
    }

效果如下:

绘制 Bitmap

用来绘制 Bitmap 对象,将 Bitmap 中的像素内容复制过来。

  • drawBitmap(Bitmap bitmap, float left, float top, Paint paint):参数说明:
    • Bitmap bitmap:Bitmap 对象。
    • float left:Bitmap 距离左边的距离。
    • float top:Bitmap 距离顶端的距离。
    • Paint paint:画笔。
  • drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint):参数说明:
    • Bitmap bitmap:Bitmap 对象。
    • Matrix matrix:用于对 Bitmap 进行转换的矩阵。
    • Paint paint:画笔。
  • drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint):参数说明:
    • Bitmap bitmap:Bitmap 对象。
    • Rect src:选取 Bitmap 中的一块,也就是该 Bitmap 需要显示的部分,如果为 null,则为整个图片。
    • Rect dst:选取部分绘制的位置。
    • Paint paint:画笔。
  • drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint):和 Rect 作用相同。

示例:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setStrokeWidth(5);//画笔笔触宽度

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
        canvas.drawBitmap(bitmap, 0, 0, paint);

        Rect rect = new Rect(0, 0, 600, 600);
        canvas.drawBitmap(bitmap, rect, new Rect(0, 700, 600, 1300), paint);

    }

显示如下:

绘制文字

通过调用 Canvas 对象的 drawText 方法来绘制文字

  • drawText(CharSequence text, int start, int end, float x, float y, Paint paint):绘制文字,参数说明:
    • CharSequence text:要绘制的文字。
    • int start:截取字符的起始索引。
    • int end:截取字符的结束索引。
    • float x:绘制起点的 X 坐标(起点为基线)。
    • float y:绘制起点的 Y 坐标(起点为基线)。
    • Paint paint:画笔。
  • drawText(String text, float x, float y, Paint paint):第二个和第三个参数分别为绘制起点的 X/Y 轴坐标。
  • drawText(char[] text, int index, int count, float x, float y, Paint paint):第三个参数为需要绘制的字符个数。
  • drawText(String text, int start, int end, float x, float y, Paint paint):和第一个函数类似。
  • drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint):通过路径绘制文字,之后会讲到。
  • drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint):通过路径绘制文字,之后会讲到。
  • drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, boolean isRtl, Paint paint):可以不用理会。
  • drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint):可以不用理会。

先来看一个简单的例子:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setTextSize(300);
        canvas.drawText("Google", 0, 0, paint);
    }

显示结果:

这是因为啥呢?画一个矩形的时候,坐标都是以四个边为标准的,而绘制文字却不是,其坐标标准是有一个“baseline”的:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int baseLine = 500;
        paint.setTextSize(300);
        canvas.drawText("Google", 200, baseLine, paint);

        //添加一条辅助线,更直观的观察 BaseLine
        paint.setStrokeWidth(5);
        paint.setColor(Color.RED);
        canvas.drawLine(0, baseLine, canvas.getWidth(), baseLine, paint);
    }

显示如下:

是不是对 drawText 的位置是不是很明白了,其实 drawText 单位绘制有很多讲究,后面再深入写吧。

总结,画一个饼图

在屏幕中间画一个饼状图,饼状图分为五部分,分别为 40%,10%,30%,15%,5%,每个部分都为不同颜色。

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.parseColor("#FFFAF0"));

        int canvasWidth = canvas.getWidth();
        int canvasHeight = canvas.getHeight();

        float left = canvasWidth / 4;
        float top = (canvasHeight - (canvasWidth / 2)) / 2;
        float right = canvasWidth * 3 / 4;
        float bottom = ((canvasHeight - (canvasWidth / 2)) / 2) + (canvasWidth / 2);

        RectF rectF = new RectF(left, top, right, bottom);

        Log.d("TTT", "width = " + canvasWidth);
        Log.d("TTT", "height = " + canvasHeight);

        Log.d("TTT","left = " + left);
        Log.d("TTT","top = " + top);
        Log.d("TTT","right = " + right);
        Log.d("TTT","bottom = " + bottom);

        //开始画饼状图。
        paint.setColor(Color.RED);
        canvas.drawArc(rectF, 0, 360 * 40 / 100, true, paint);
        paint.setColor(Color.BLUE);
        canvas.drawArc(rectF, 360 * 40 / 100, 360 * 10 / 100, true, paint);
        paint.setColor(Color.GREEN);
        canvas.drawArc(rectF, (360 * 40 / 100) + (360 * 10 / 100), 360 * 30 / 100, true, paint);
        paint.setColor(Color.GRAY);
        canvas.drawArc(rectF, (360 * 40 / 100) + (360 * 10 / 100) + (360 * 30 / 100), 360 * 15 / 100, true, paint);
        paint.setColor(Color.YELLOW);
        canvas.drawArc(rectF, (360 * 40 / 100) + (360 * 10 / 100) + (360 * 30 / 100) + (360 * 15 / 100), 360 * 5 / 100, true, paint);
        //中间画一个圆,让这个饼状图变成空心。
        paint.setColor(Color.parseColor("#FFFAF0"));
        canvas.drawCircle(canvasWidth / 2, canvasHeight / 2, 200, paint);
    }

参数没啥可解释的,简单的加减法,显示如下:

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1