Canvas

来源:互联网 发布:cici网络里是什么意思 编辑:程序博客网 时间:2024/04/28 09:58

API—Canvas | Android 开发者
Canvas介绍
Canvas(画布):可以理解成画布。

嵌套类

1.Canvas.EdgeType

API—Canvas.EdgeType | Android 开发者

2.Canvas.VertexMode

API—Canvas.VertexMode | Android 开发者

常量

标记 说明 ALL_SAVE_FLAG 保存全部的状态 CLIP_SAVE_FLAG 保存裁剪的某个区域的状态 CLIP_TO_LAYER_SAVE_FLAG 保存预先设置的范围里的状态 FULL_COLOR_LAYER_SAVE_FLAG 保存彩色涂层 HAS_ALPHA_LAYER_SAVE_FLAG 不透明图层保存 MATRIX_SAVE_FLAG Matrix信息(translate,rotate,scale,skew)的状态保存

构造方法

Canvas的构造方法有两种:

Canvas(): 创建一个空的画布,可以使用setBitmap()方法来设置绘制具体的画布。Canvas(Bitmap bitmap): 以bitmap对象创建一个画布,将内容都绘制在bitmap上,因此bitmap不得为null。

接着是

1.drawXXX()方法族:

以一定的坐标值在当前画图区域画图,另外图层会叠加, 即后面绘画的图层会覆盖前面绘画的图层。 比如:

Canvas坐标系与绘图坐标系

Canvas绘图中牵扯到两种坐标系:Canvas坐标系与绘图坐标系。

Canvas坐标系

Canvas坐标系指的是Canvas本身的坐标系,Canvas坐标系有且只有一个,且是唯一不变的,其坐标原点在View的左上角,从坐标原点向右为x轴的正半轴,从坐标原点向下为y轴的正半轴。

绘图坐标系

Canvas的drawXXX方法中传入的各种坐标指的都是绘图坐标系中的坐标,而非Canvas坐标系中的坐标。默认情况下,绘图坐标系与Canvas坐标系完全重合,即初始状况下,绘图坐标系的坐标原点也在View的左上角,从原点向右为x轴正半轴,从原点向下为y轴正半轴。但不同于Canvas坐标系,绘图坐标系并不是一成不变的,可以通过调用Canvas的translate方法平移坐标系,可以通过Canvas的rotate方法旋转坐标系,还可以通过Canvas的scale方法缩放坐标系,而且需要注意的是,translate、rotate、scale的操作都是基于当前绘图坐标系的,而不是基于Canvas坐标系,一旦通过以上方法对坐标系进行了操作之后,当前绘图坐标系就变化了,以后绘图都是基于更新的绘图坐标系了。也就是说,真正对我们绘图有用的是绘图坐标系而非Canvas坐标系。

为了更好的理解绘图坐标系,请看如下的代码:

    // 绘制坐标系    private void drawAxis(Canvas canvas) {        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setStrokeCap(Paint.Cap.ROUND);        mPaint.setStrokeWidth(6 * density);        // 用绿色画X轴,用蓝色画Y轴        // 第一次绘制坐标轴        mPaint.setColor(Color.GREEN);        canvas.drawLine(0, 0, canvasWidth, 0, mPaint);// 绘制X轴        mPaint.setColor(Color.BLUE);        canvas.drawLine(0, 0, 0, canvasHeight, mPaint);// 绘制Y轴        // 对坐标进行平移,第二次绘制坐标轴        canvas.translate(canvasWidth / 4, canvasHeight / 4);// 向左向右各平移屏幕的1/4        mPaint.setColor(Color.GREEN);        canvas.drawLine(0, 0, canvasWidth, 0, mPaint);// 绘制X轴        mPaint.setColor(Color.BLUE);        canvas.drawLine(0, 0, 0, canvasHeight, mPaint);// 绘制Y轴        // 再次平移坐标轴,然后旋转,第三次绘制坐标轴        canvas.translate(canvasWidth / 4, canvasHeight / 4);// 向左向右各平移屏幕的1/4        canvas.rotate(45);// 顺时针旋转45°角        mPaint.setColor(Color.GREEN);        canvas.drawLine(0, 0, canvasWidth, 0, mPaint);// 绘制X轴        mPaint.setColor(Color.BLUE);        canvas.drawLine(0, 0, 0, canvasHeight, mPaint);// 绘制Y轴    }

界面如下所示:
这里写图片描述

第一次绘制绘图坐标系时,绘图坐标系默认情况下和Canvas坐标系重合,所以绘制出的坐标系紧贴View的上侧和左侧;
第二次首先将坐标轴向右下角平移了一段距离,然后绘制出的坐标系也就整体向右下角平移了;
第三次再次向右下角平移,并旋转了45度,图上倾斜的坐标系即最后的绘图坐标系。

drawARGB

drawARGB(int a, int r, int g, int b):第一个参数,Alpha通道,透明度,0-255,第二、三、四个参数,分别是三原色R(Red)、G(Green)、B(Blue)都是16进制的0-255drawRGB(int r, int g, int b):
    private void drawArgb(Canvas canvas) {        canvas.drawARGB(255, 139, 197, 186);        // 第一个参数,Alpha,透明度0-255        // 第二、三、四个参数,分别是三原色R(Red)、G(Green)、B(Blue)        // 都是16进制的0-255    }

界面如下所示:
这里写图片描述

drawPoint的基本使用

drawPoint(float x, float y, Paint paint): 画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。
    private void drawPoint(Canvas canvas) {        mPaint.setColor(0xff8bc5ba);// 设置颜色        mPaint.setStrokeWidth(30 * density);// 设置线宽,如果不设置线宽,无法绘制点        int x = canvasWidth / 2;// 水平居中        int deltaY = canvasHeight / 3;// 将屏幕分为三份        int y = deltaY / 2;// 第一份的中间        // 绘制Cap为BUTT的点        mPaint.setStrokeCap(Paint.Cap.BUTT);        canvas.drawPoint(x, y, mPaint);        // 绘制Cap为ROUND的点        canvas.translate(0, deltaY);// 将画板往下移动一份        mPaint.setStrokeCap(Paint.Cap.ROUND);        canvas.drawPoint(x, y, mPaint);        // 绘制Cap为SQUARE的点        canvas.translate(0, deltaY);// 将画板往下移动一份        mPaint.setStrokeCap(Paint.Cap.SQUARE);        canvas.drawPoint(x, y, mPaint);        // 注意:        // 1 如果的单独画一个点,那么ROUND和SQUARE相对于BUTT,是没有任何多余的长度的        // 2 如果用画点 来模仿画矩形或者画圆,是否可以行?当然是可行的        // 3 如果线宽比较大,那么x,y表示的点是图形的左上角还是中心点?当然是中心点啦    }

界面如下所示:
这里写图片描述
下面对以上代码进行说明:

  1. Paint的setStrokeWidth方法可以控制所画线的宽度,通过Paint的getStrokeWidth方法可以得到所画线的宽度,默认情况下,线宽是0。其实strokeWidth不仅对画线有影响,对画点也有影响,由于默认的线宽是0,所以默认情况下调用drawPoint方法无法在Canvas上画出点,为了让大家清楚地看到所画的点,我用Paint的setStrokeWidth设置了一个比较大的线宽,这样我们看到的点也就比较大。

  2. Paint有个setStrokeCap方法可以设置所画线段的时候两个端点的形状,即所画线段的帽端的形状,在下面讲到drawLine方法时会详细说明,其实setStrokeCap方法也会影响所画点的形状。Paint的setStrokeCap方法可以有三个取值:Paint.Cap.BUTT、Paint.Cap.ROUND和Paint.Cap.SQUARE。

  3. 默认情况下Paint的getStrokeCap的返回值是Paint.Cap.BUTT,默认画出来的点就是一个正方形,上图第一个点即是用BUTT作为帽端画的。

  4. 我们可以调用setStrokeCap方法设置Paint的strokeCap为Paint.Cap.ROUND时,画笔画出来的点就是一个圆形,上图第二个点即是用ROUND作为帽端画的。

  5. 调用调用setStrokeCap方法设置Paint的strokeCap为Paint.Cap.SQUARE时,画笔画出来的电也是一个正方形,与用BUTT画出来的效果在外观上相同,上图最后一个点即时用SQUARE作为帽端画的。

drawPoint的重载方法

drawPoints(float[] pts, Paint paint):pts必须是2的倍数,比如pts={x1,y1,x2,y2,x3,y3,x4,y4},画的就是四个点。drawPoints(float[] pts, int offset, int count, Paint paint):pts与上个方法相同,offset是pts的起始元素下标,第一个元素是0,第三个是2count是提取多少个元素来画点,count必须是2的倍数。
private void drawPoint2(Canvas canvas) {        mPaint.setColor(Color.RED);// 设置颜色        mPaint.setStrokeWidth(30 * density);// 设置线宽,如果不设置线宽,无法绘制点        int deltaX = canvasWidth / 4;// 水平分成4份        int x = deltaX / 2;// 水平第一份的中间        int deltaY = canvasHeight / 4;// 将垂直分为4份        int y = deltaY / 2;// 垂直第一份的中间        mPaint.setStrokeCap(Paint.Cap.BUTT);        float[] pts1 = new float[] { x, y, deltaX + x, y, 2 * deltaX + x, y };// 三个点        float[] pts2 = new float[] { x, y, deltaX + x, y, 2 * deltaX + x, y,                3 * deltaX + x };// 三个点加一个x坐标        canvas.drawPoints(pts1, mPaint);        // canvas.drawPoints(pts2, mPaint);//pts2的个数是奇数,会报错        canvas.translate(0, deltaY);// 将画板往下移动一份        canvas.drawPoints(pts1, 0, 4, mPaint);// count=1        // offset:从pts的第几个元素开始取值,第一个为0,第二个为1,第三个为2        // count:要获得数组的元素个数,必为偶数        // 注意:        // pts:多个像素点的坐标,元素个数必须是偶数,两个一组为一个像素点的横纵坐标    }

界面如下所示:
这里写图片描述

drawLine

drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) : 画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置, 参数四y轴垂直位置,最后一个参数为Paint 画刷对象。drawLines(float[] pts, Paint paint):pts的元素总数必须是4的倍数,否则报错,比如pts={x1,y1,x2,y2,x3,y3,x4,y4},画线就是从(x1,y1)到(x2,y2),然后(x3,y3)到(x4,y5)。drawLines(float[] pts, int offset, int count, Paint paint):pts与上个方法相同,offset是pts的起始元素下标,第一个元素是0,第三个是2,count是提取多少个元素来画线,count必须是2的倍数。
    private void drawLine(Canvas canvas) {        // 看完drawPoint重载方法,这个就很简单了        mPaint.setColor(Color.RED);// 设置颜色        mPaint.setStrokeWidth(20 * density);// 设置线宽,如果不设置线宽,无法绘制点        int deltaX = canvasWidth / 4;// 水平分成4份        int x = deltaX / 2;// 水平第一份的中间        int deltaY = canvasHeight / 4;// 将垂直分为4份        int y = deltaY / 2;// 垂直第一份的中间        mPaint.setStyle(Paint.Style.FILL);        // 绘制Cap为BUTT的点        mPaint.setStrokeCap(Paint.Cap.BUTT);        canvas.drawLine(x, y, deltaX + x, y, mPaint);        // 绘制Cap为ROUND的点        canvas.translate(0, deltaY);// 将画板往下移动一份        mPaint.setStrokeCap(Paint.Cap.ROUND);        canvas.drawLine(x, y, deltaX + x, y, mPaint);        // 绘制Cap为SQUARE的点        canvas.translate(0, deltaY);// 将画板往下移动一份        mPaint.setStrokeCap(Paint.Cap.SQUARE);        canvas.drawLine(x, y, deltaX + x, y, mPaint);        // 画一个折线        mPaint.setStrokeWidth(2 * density);        canvas.translate(0, deltaY);// 将画板往下移动一份        // 这里的pts要求是4的倍数        float[] pts = new float[] { x, y, deltaX + x, y + 30, deltaX + x,                y + 30, 2 * deltaX + x, y - 30 };        canvas.drawLines(pts, mPaint);        // 注意:        // pts要求是4的倍数    }

界面如下所示:
这里写图片描述
下面对以上代码进行说明:

  1. drawLine方法接收四个数值,即起点的x和y以及终点的x和y,绘制一条线段。

  2. drawLines方法接收一个float数组pts,需要注意的是在用drawLines绘图时,其每次从pts数组中取出四个点绘制一条线段,然后再取出后面四个点绘制一条线段,所以要求pts的长度需要是4的倍数。假设我们有四个点,分别是p1、p2、p3、p4,我们依次将其坐标放到pts数组中,即pts
    = {p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y},那么用drawLines绘制pts时,你会发现p1和p2之间画了一条线段,p3和p4之间画了一条线段,但是p2和p3之间没有画线段,这样大家就应该能明白drawLines每次都需要从pts数组中取出4个值绘制一条线段的意思了。

  3. 通过调用Paint的setStrokeWidth方法设置线的宽度。

  4. 上面在讲drawPoint时提到了strokeCap对所绘制点的形状的影响,通过drawLine绘制的线段也受其影响,体现在绘制的线段的两个端点的形状上。

    • Paint.Cap.BUTT
      当用BUTT作为帽端时,所绘制的线段恰好在起点终点位置处戛然而止,两端是方形,上图中第一条加粗的线段就是用BUTT作为帽端绘制的。

    • Paint.Cap.ROUND
      当用ROUND作为帽端时,所绘制的线段的两端端点会超出起点和终点一点距离,并且两端是圆形状,上图中第二条加粗的线段就是用ROUND作为帽端绘制的。

    • Paint.Cap.SQUARE
      当用SQUARE作为帽端时,所绘制的线段的两端端点也会超出起点和终点一点距离,两端点的形状是方形,上图中最后一条加粗的线段就是用SQUARE作为帽端绘制的。

drawCircle和drawOval

画圆和椭圆差不多

drawCircle

drawCircle(float cx, float cy, float radius,Paint paint): 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象;

drawOval

drawOval(RectF oval, Paint paint):画椭圆,参数一是扫描区域,参数二为paint对象;
    private void drawCircle(Canvas canvas) {        // 圆是圆心加半径,椭圆就是一个外包矩形,然后设置画笔的style        mPaint.setColor(0xff8bc5ba);// 设置颜色        mPaint.setStrokeWidth(5 * density);// 设置线宽,如果不设置线宽,无法绘制点        int x = canvasWidth / 2;// 水平居中        int deltaY = canvasHeight / 2;// 将屏幕分为三份        int y = deltaY / 2;// 第一份的中间        // 画圆,设置style是Full        mPaint.setStyle(Paint.Style.FILL);        canvas.drawCircle(x, y, y - 10, mPaint);// 第一个是圆心的x和y坐标,第三个是半径        canvas.translate(0, deltaY);// 将画板往下移动一份        // 画椭圆        RectF oval = new RectF(10, 10, canvasWidth - 10, deltaY - 10);// 左上右下        canvas.drawOval(oval, mPaint);    }

界面如下所示:
这里写图片描述
关于Paint.Style 请移步
Paint—2—Cap、Style、Join、Align

drawArc

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint): 画弧,参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角 (度)在电弧的开始,参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电 弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;
    private void drawArc(Canvas canvas) {        // 这个也很简单,四个参数        // 第一个参数为外包矩形,第二个参数为起始角度,从中心水平向右为0°,顺时针旋转,第三个参数是旋转多少度,第四个参数是是否连接中心        int canvasWidth = canvas.getWidth();        int canvasHeight = canvas.getHeight();        int count = 5;        float ovalHeight = canvasHeight / (count + 1);        float left = 10 * density;        float top = 0;        float right = canvasWidth - left;        float bottom = ovalHeight;        RectF rectF = new RectF(left, top, right, bottom);        mPaint.setStrokeWidth(2 * density);// 设置线宽        mPaint.setColor(0xff8bc5ba);// 设置颜色        mPaint.setStyle(Paint.Style.FILL);// 默认设置画笔为填充模式        // 绘制用drawArc绘制完整的椭圆        canvas.translate(0, ovalHeight / count);        canvas.drawArc(rectF, 0, 360, true, mPaint);        // 绘制椭圆的四分之一,起点是钟表的3点位置,从3点绘制到6点的位置        canvas.translate(0, (ovalHeight + ovalHeight / count));        canvas.drawArc(rectF, 0, 90, true, mPaint);        // 绘制椭圆的四分之一,将useCenter设置为false        canvas.translate(0, (ovalHeight + ovalHeight / count));        canvas.drawArc(rectF, 0, 90, false, mPaint);        // 绘制椭圆的四分之一,只绘制轮廓线        mPaint.setStyle(Paint.Style.STROKE);// 设置画笔为线条模式        canvas.translate(0, (ovalHeight + ovalHeight / count));        canvas.drawArc(rectF, 0, 90, true, mPaint);        // 绘制带有轮廓线的椭圆的四分之一        // 1. 先绘制椭圆的填充部分        mPaint.setStyle(Paint.Style.FILL);// 设置画笔为填充模式        canvas.translate(0, (ovalHeight + ovalHeight / count));        canvas.drawArc(rectF, 0, 90, true, mPaint);        // 2. 再绘制椭圆的轮廓线部分        mPaint.setStyle(Paint.Style.STROKE);// 设置画笔为线条模式        mPaint.setColor(0xff0000ff);// 设置轮廓线条为蓝色        canvas.drawArc(rectF, 0, 90, true, mPaint);    }

界面如下所示:
这里写图片描述
下面对以上代码进行说明:

  1. 用drawArc画的弧指的是椭圆弧,即椭圆的一部分。当然,如果椭圆的长轴和和短轴相等,这时候我们就可以用drawArc方法绘制圆弧。其方法签名是:

    public void drawArc (RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
    • oval是RecF类型的对象,其定义了椭圆的形状。
    • startAngle指的是绘制的起始角度,钟表的3点位置对应着0度,如果传入的startAngle小于0或者大于等于360,那么用startAngle对360进行取模后作为起始绘制角度。
    • sweepAngle指的是从startAngle开始沿着钟表的顺时针方向旋转扫过的角度。如果sweepAngle大于等于360,那么会绘制完整的椭圆弧。如果sweepAngle小于0,那么会用sweepAngle对360进行取模后作为扫过的角度。
    • useCenter是个boolean值,如果为true,表示在绘制完弧之后,用椭圆的中心点连接弧上的起点和终点以闭合弧;如果值为false,表示在绘制完弧之后,弧的起点和终点直接连接,不经过椭圆的中心点。
  2. 在代码中我们一开始设置的Paint的style为FILL,即填充模式。通过上面的描述我们知道,drawOval方法可以看做是drawArc方法的一种特例。如果在drawArc方法中sweepAngle为360,无论startAngle为多少,drawArc都会绘制一个椭圆,如上图中第一个图形,我们用canvas.drawArc(rectF,
    0, 360, true, paint)绘制了一个完整的椭圆,就像用drawOval画出的那样。

  3. 当我们调用方法canvas.drawArc(rectF, 0, 90, true, paint)时,
    我们指定了起始角度为0,然后顺时针绘制90度,即我们会绘制从3点到6点这90度的弧,如上图中第二个图形所示,我们绘制了一个椭圆的右下角的四分之一的弧面,需要注意的是我们此处设置的useCenter为true,所以弧上的起点(3点位置)和终点(6点位置)都和椭圆的中心连接了形成了。

  4. 当我们调用方法canvas.drawArc(rectF, 0, 90, false,
    paint)时,我们还是绘制椭圆右下角的弧面,不过这次我们将useCenter设置成了false,如上图中的第三个图形所示,弧上的起点(3点位置)和终点(6点位置)直接相连闭合了,而没有经过椭圆的中心点。

  5. 上面介绍到的绘图都是在画笔Paint处于FILL状态下绘制的。我们可以通过paint.setStyle(Paint.Style.STROKE)方法将画笔的style改为STROKE,即绘制线条模式。然后我们再次执行canvas.drawArc(rectF,0, 90, true,paint),初始角度为0,扫过90度的区域,useCenter为true,绘制的效果见上图中第四个图形,此时我们只绘制了椭圆的轮廓线。需要注意的,由于Paint默认的线宽为0,所以在绘制之前要确保掉用过Paint.setStrokeWidth()方法以设置画笔的线宽。

  6. 如果我们想绘制出带有其他颜色轮廓线的弧面时,该怎么办呢?我们可以分两步完成:首先,将画笔Paint的style设置为FILL模式,通过drawArc方法绘制出弧面。然后,将画笔Paint的style设置为STROKE模式,并通过paint的setColor()方法改变画笔的颜色,最后drawArc方法绘制出弧线。这样我们就能绘制出带有其他颜色轮廓线的弧面了,如上图中最后一个图形所示。

drawRect

drawRect(float left, float top, float right, float bottom, Paint paint)drawRect(RectF rect, Paint paint) :绘制区域,参数一为RectF一个区域drawRect(Rect r, Paint paint)drawRoundRect(RectF rect, float rx, float ry, Paint paint)drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint): 画弧,参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角 (度)在电弧的开始,参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电 弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;
    private void drawRect(Canvas canvas) {        // 画矩形也很简单,就是四个边界参数,说白了就是左上角和右下角的坐标,都是一回事        mPaint.setColor(0xff8bc5ba);// 设置颜色        mPaint.setStrokeWidth(5 * density);// 设置线宽,如果不设置线宽,无法绘制点        mPaint.setStyle(Paint.Style.STROKE);        int x = canvasWidth / 2;// 水平居中        int deltaY = canvasHeight / 2;// 将屏幕分为2份        int y = deltaY / 2;// 第一份的中间        // 直接用四个边界参数画矩形        canvas.drawRect(50, 50, canvasWidth - 50, deltaY - 50, mPaint);        canvas.translate(0, deltaY);// 将画板往下移动一份        // 画圆角矩形,第二三个参数,分别是圆角椭圆的x和y        canvas.drawRoundRect(new RectF(50, 50, canvasWidth - 50,                deltaY - 50), 15, 15, mPaint);    }

界面如下所示:
这里写图片描述

drawPath

drawPath(Path path, Paint paint) :绘制一个路径,参数一为Path路径对象
    private void drawPath(Canvas canvas) {        int deltaX = canvasWidth / 4;        int deltaY = (int)(deltaX * 0.75);        mPaint.setColor(0xff8bc5ba);//设置画笔颜色        mPaint.setStrokeWidth(4);//设置线宽        /*--------------------------用Path画填充面-----------------------------*/        mPaint.setStyle(Paint.Style.FILL);//设置画笔为填充模式        Path path = new Path();        //向Path中加入Arc        RectF arcRecF = new RectF(0, 0, deltaX, deltaY);        path.addArc(arcRecF, 0, 135);        //向Path中加入Oval        RectF ovalRecF = new RectF(deltaX, 0, deltaX * 2, deltaY);        path.addOval(ovalRecF, Path.Direction.CCW);        //向Path中添加Circle        path.addCircle((float)(deltaX * 2.5), deltaY / 2, deltaY / 2, Path.Direction.CCW);        //向Path中添加Rect        RectF rectF = new RectF(deltaX * 3, 0, deltaX * 4, deltaY);        path.addRect(rectF, Path.Direction.CCW);        canvas.drawPath(path, mPaint);        /*--------------------------用Path画线--------------------------------*/        mPaint.setStyle(Paint.Style.STROKE);//设置画笔为线条模式        canvas.translate(0, deltaY * 2);        Path path2 = path;        canvas.drawPath(path2, mPaint);        /*-----------------使用lineTo、arcTo、quadTo、cubicTo画线--------------*/        mPaint.setStyle(Paint.Style.STROKE);//设置画笔为线条模式        canvas.translate(0, deltaY * 2);        Path path3 = new Path();        //用pointList记录不同的path的各处的连接点        List<Point> pointList = new ArrayList<Point>();        //1. 第一部分,绘制线段        path3.moveTo(0, 0);        path3.lineTo(deltaX / 2, 0);//绘制线段        pointList.add(new Point(0, 0));        pointList.add(new Point(deltaX / 2, 0));        //2. 第二部分,绘制椭圆右上角的四分之一的弧线        RectF arcRecF1 = new RectF(0, 0, deltaX, deltaY);        path3.arcTo(arcRecF1, 270, 90);//绘制圆弧        pointList.add(new Point(deltaX, deltaY / 2));        //3. 第三部分,绘制椭圆左下角的四分之一的弧线        //注意,我们此处调用了path的moveTo方法,将画笔的移动到我们下一处要绘制arc的起点上        path3.moveTo(deltaX * 1.5f, deltaY);        RectF arcRecF2 = new RectF(deltaX, 0, deltaX * 2, deltaY);        path3.arcTo(arcRecF2, 90, 90);//绘制圆弧        pointList.add(new Point((int)(deltaX * 1.5), deltaY));        //4. 第四部分,绘制二阶贝塞尔曲线        //二阶贝塞尔曲线的起点就是当前画笔的位置,然后需要添加一个控制点,以及一个终点        //再次通过调用path的moveTo方法,移动画笔        path3.moveTo(deltaX * 1.5f, deltaY);        //绘制二阶贝塞尔曲线        path3.quadTo(deltaX * 2, 0, deltaX * 2.5f, deltaY / 2);        pointList.add(new Point((int)(deltaX * 2.5), deltaY / 2));        //5. 第五部分,绘制三阶贝塞尔曲线,三阶贝塞尔曲线的起点也是当前画笔的位置        //其需要两个控制点,即比二阶贝赛尔曲线多一个控制点,最后也需要一个终点        //再次通过调用path的moveTo方法,移动画笔        path3.moveTo(deltaX * 2.5f, deltaY / 2);        //绘制三阶贝塞尔曲线        path3.cubicTo(deltaX * 3, 0, deltaX * 3.5f, 0, deltaX * 4, deltaY);        pointList.add(new Point(deltaX * 4, deltaY));        //Path准备就绪后,真正将Path绘制到Canvas上        canvas.drawPath(path3, mPaint);        //最后绘制Path的连接点,方便我们大家对比观察        mPaint.setStrokeWidth(10);//将点的strokeWidth要设置的比画path时要大        mPaint.setStrokeCap(Paint.Cap.ROUND);//将点设置为圆点状        mPaint.setColor(0xff0000ff);//设置圆点为蓝色        for(Point p : pointList){            //遍历pointList,绘制连接点            canvas.drawPoint(p.x, p.y, mPaint);        }    }

界面如下所示:
这里写图片描述

下面对以上代码进行说明:

  1. Canvas的drawPath()方法接收Path和Paint两个参数。当Paint的style是FILL时,我们可以用darwPath来画填充面。Path类提供了addArc、addOval、addCircle、addRect等方法,可以通过这些方法可以向Path添加各种闭合图形,Path甚至还提供了addPath方法让我们将一个Path对象添加到另一个Path对象中作为其一部分。当我们通过Path的addXXX方法向Path中添加了各种图形后,我们就可以调用canvas.drawPath(path,
    paint)绘制出Path了,如上图中第一行中的几个图形所示。

  2. 我们可以通过调用Paint的setStyle()方法将画笔Paint设置为STROKE,即线条模式,
    然后我们再次执行canvas.darwPath()方法绘制同一个Path对象,我们这次绘制的就只是Path的轮廓线了,如上图中第二行中的几个图形所示。

  3. Path对象还有很多xxTo方法,比如lineTo、arcTo、quadTo、cubicTo等,通过这些方法,我们可以方便的从画笔位置绘制到指定坐标的连续线条,如上图中最后一行的几个线状图形所示。我们用了lineTo、arcTo、quadTo、cubicTo这四种方法画了五段线条,下面会解释,并且单独通过调用drawPoint画出了每段线条的两个端点,方便大家观察。

    • moveTo方法用于设置下一个线条的起始点,可以认为是移动了画笔,但说移动画笔不严格,后面会解释,此处大家暂且这么理解。

    • lineTo的方法签名是public void lineTo (float x, float y),Path的lineTo方法会从当前画笔的位置到我们指定的坐标构建一条线段,然后将其添加到Path对象中,如上图中最后一行图形中的第一条线段所示。

    • arcTo的方法签名是public void arcTo (RectF oval, float startAngle, float sweepAngle),oval、startAngle与sweepAngle的参数与之前提到的darwArc方法对应的形参意义相同,在此不再赘述。Path的arcTo方法会构建一条弧线并添加到Path对象中,如上图中最后一行图形中的第二条和第三条线状图形所示,这两条弧线都是通过Path的arcTo方法添加的。

    • quadTo是用来画二阶贝塞尔曲线的,即抛物线,其方法签名是public void quadTo (float x1, float y1, float x2, float y2),如果对贝塞尔曲线的相关概念不了解,推荐大家读一下博文《贝塞尔曲线初探》 。下面借用该博文中的一张图说一下二阶贝塞尔曲线:
      这里写图片描述
      二阶贝塞尔曲线的绘制一共需要三个点,一个起点,一个终点,还要有一个中间的控制点。我们画笔的位置就相当于上图中P0的位置,quadTo中的前两个参数x1和y1指定了控制点P1的坐标,后面两个参数x2和y2指定了终点P2的坐标。上图中最后一行的第四个线状图形就是用quadTo绘制的二阶贝塞尔曲线。

    • cubicTo跟quadTo类似,不过是用来画三阶贝塞尔曲线的,其方法签名是public void cubicTo (float x1, float y1, float x2, float y2, float x3, float y3)。我们还是借用一下上述博文《贝塞尔曲线初探》中的另一张图片来解释一下三阶贝塞尔曲线:
      这里写图片描述
      三阶贝塞尔曲线的绘制需要四个点,一个起点,一个终点,以及两个中间的控制点,也就是说它比二阶贝塞尔曲线要多一个控制点。我们画笔的位置就相当于上图中P0的位置,cubicTo中的前两个参数x1和y1指定了第一个控制点P1的坐标,参数x2和y2指定了第二个控制点P2的坐标,最后两个参数x3和y3指定了终点P3的坐标。上图中最后一行的最后一个线状图形就是用cubicTo绘制的三阶贝塞尔曲线。

  4. 上面提到Path的moveTo方法移动了画笔的位置,这样说不准确,因为Path和Paint没有任何关系,准确的说法是移动了Path的当前点,当我们调用lineTo、arcTo、quadTo、cubicTo等方法时,首先要从当前点开始绘制。对于lineTo、quadTo、cubicTo这三个方法来说,Path的当前点作为了这三个方法绘制的线条中的起始点,但是对于arcTo方法来说却不同。当我们调用arcTo方法时,首先会从Path的当前点画一条直线到我们所画弧的起始点,所以在使用Path的arcTo方法前要注意通过调用Path的moveTo方法使当前点与所画弧的起点重合,否则有可能你就会看到多了一条当前点到弧的起点的线段。moveTo可以移动当前点,当调用了lineTo、arcTo、quadTo、cubicTo等方法时,当前点也会移动,当前点就变成了所绘制的线条的最后一个点。

  5. 上面提到了moveTo、lineTo、arcTo、quadTo、cubicTo的方法中传入的坐标都是绘图坐标系中的坐标,即绘图坐标系中的绝对坐标。其实我们可以用相对坐标调用这些类型功能的方法。Path因此提供了对应的rMoveTo、rLineTo、rQuadTo、rCubicTo方法,其形参列表与对应的方法相同,只不过里面传入的坐标不是相对于当前点的相对坐标,即传入的坐标是相对于当前点的偏移值。

  6. lineTo、arcTo、quadTo、cubicTo等方法只是向Path中添加相应的线条,只有执行了canvas.drawPath(path3,
    paint)方法时,我们才能将Path绘制到Canvas上。

drawText

drawText(String text, float x, floaty, Paint paint) : 渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本, 参数二x轴,参数三y轴,注意是文字的左下角,参数四是Paint对象。drawText(CharSequence text, int start, int end, float x, float y, Paint paint)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)
    private void drawText(Canvas canvas) {        int halfCanvasWidth = canvasWidth / 2;        float translateY = textHeight;        mPaint.setStyle(Paint.Style.FILL);//绘制文字是用点绘制的,一定要设置style为FILL        mPaint.setStrokeWidth(4);        mPaint.setTextSize(50);        //绘制正常文本        canvas.save();        canvas.translate(0, translateY);        canvas.drawText("正常绘制文本", 0, 0, mPaint);        canvas.restore();        translateY += textHeight * 2;        //绘制绿色文本        mPaint.setColor(0xff00ff00);//设置字体为绿色        canvas.save();        canvas.translate(0, translateY);//将画笔向下移动        canvas.drawText("绘制绿色文本", 0, 0, mPaint);        canvas.restore();        mPaint.setColor(0xff000000);//重新设置为黑色        translateY += textHeight * 2;        //设置左对齐        mPaint.setTextAlign(Paint.Align.LEFT);//设置左对齐        canvas.save();        canvas.translate(halfCanvasWidth, translateY);        canvas.drawText("左对齐文本", 0, 0, mPaint);        canvas.restore();        translateY += textHeight * 2;        //设置居中对齐        mPaint.setTextAlign(Paint.Align.CENTER);//设置居中对齐        canvas.save();        canvas.translate(halfCanvasWidth, translateY);        canvas.drawText("居中对齐文本", 0, 0, mPaint);        canvas.restore();        translateY += textHeight * 2;        //设置右对齐        mPaint.setTextAlign(Paint.Align.RIGHT);//设置右对齐        canvas.save();        canvas.translate(halfCanvasWidth, translateY);        canvas.drawText("右对齐文本", 0, 0, mPaint);        canvas.restore();        mPaint.setTextAlign(Paint.Align.LEFT);//重新设置为左对齐        translateY += textHeight * 2;        //设置下划线        mPaint.setUnderlineText(true);//设置具有下划线        canvas.save();        canvas.translate(0, translateY);        canvas.drawText("下划线文本", 0, 0, mPaint);        canvas.restore();        mPaint.setUnderlineText(false);//重新设置为没有下划线        translateY += textHeight * 2;        //绘制加粗文字        mPaint.setFakeBoldText(true);//将画笔设置为粗体        canvas.save();        canvas.translate(0, translateY);        canvas.drawText("粗体文本", 0, 0, mPaint);        canvas.restore();        mPaint.setFakeBoldText(false);//重新将画笔设置为非粗体状态        translateY += textHeight * 2;        //文本绕绘制起点顺时针旋转        canvas.save();        canvas.translate(0, translateY);        canvas.rotate(20);        canvas.drawText("文本绕绘制起点旋转20度", 0, 0, mPaint);        canvas.restore();        translateY += 200;        //根据path绘制文字        canvas.save();        canvas.translate(0, translateY);        Path path=new Path();        path.moveTo(50, 50);        path.lineTo(100, 100);        path.lineTo(200, 200);        path.lineTo(300, 50);//        path.close();//也可以不封闭        canvas.drawTextOnPath("文字跟着path走,超过长度就停", path, 50, 50, mPaint);    //绘制文字        canvas.restore();    }

界面如下所示:
这里写图片描述

对以上代码进行一下说明:

  1. Android中的画笔有两种Paint和TextPaint,我们可以Paint来画其他的图形:点、线、矩形、椭圆等。TextPaint继承自Paint,是专门用来画文本的,由于TextPaint继承自Paint,所以也可以用TextPaint画点、线、面、矩形、椭圆等图形。

  2. 我们在上面的代码中将canvas.translate()和canvas.rotate()放到了canvas.save()和canvas.restore()之间,这样做的好处是,在canvas.save()调用时,将当前坐标系保存下来,将当前坐标系的矩阵Matrix入栈保存,然后通过translate或rotate等对坐标系进行变换,然后进行绘图,绘图完成后,我们通过调用canvas.restore()将之前保存的Matrix出栈,这样就将当前绘图坐标系恢复到了canvas.save()执行的时候状态。如果熟悉OpenGL开发,对这种模式应该很了解。

  3. 通过调用paint.setColor(0xff00ff00)将画笔设置为绿色,paint的setColor方法需要传入一个int值,通常情况下我们写成16进制0x的形式,第一个字节存储Alpha通道,第二个字节存储Red通道,第三个字节存储Green通道,第四个字节存储Blue通道,每个字节的取值都是从00到ff。如果对这种设置颜色的方式不熟悉,也可以调用paint.setARGB(int
    a, int r, int g, int b)方法设置画笔的颜色,不过paint.setColor(int color)的方式更简洁。
  4. 通过调用paint.setTextAlign()设置文本的对齐方式,该对齐方式是相对于绘制文本时的画笔的坐标来说的,在本例中,我们绘制文本时画笔在Canvas宽度的中间。在drawText()方法执行时,需要传入一个x和y坐标,假设该点为P点,P点表示我们从P点绘制文本。当对齐方式为Paint.Align.LEFT时,绘制的文本以P点为基准向左对齐,这是默认的对齐方式;当对齐方式为Paint.Align.CENTER时,绘制的文本以P点为基准居中对齐;当对齐方式为Paint.Align.RIGHT时,绘制的文本以P点为基准向右对齐。

  5. 通过调用paint.setUnderlineText(true)绘制带有下划线的文本。

  6. 通过调用paint.setFakeBoldText(true)绘制粗体文本。

  7. 通过rotate旋转坐标系,我们可以绘制倾斜文本。

drawBitmap

drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint)drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) : 贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap), 参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象, 因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。drawBitmap(Bitmap bitmap, float left, float top, Paint paint)://Bitmap:图片对象,left:偏移左边的位置,top: 偏移顶部的位置drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
    private void drawBitmap(Canvas canvas){        //如果bitmap不存在,那么就不执行下面的绘制代码        if(bitmap == null){            return;        }        Rect src = new Rect();// 图片的显示部分        Rect dst = new Rect();// 屏幕位置及尺寸,供图片显示的区域        //src 这个是表示绘画图片的大小        src.left = 0;   //0,0          src.top = 0;        src.right = 100;        src.bottom = 100;        // 下面的 dst 是表示 绘画这个图片的位置        dst.left = 0;            dst.top = 0;            dst.right = 200;          dst.bottom = 200;            canvas.drawBitmap(bitmap, src, dst, mPaint);//这个方法  第一个参数是图片原来的大小,第二个参数是 绘画该图片需显示多少。也就是说你想绘画该图片的某一些地方,而不是全部图片,第三个参数表示该图片绘画的位置        canvas.translate(0, 210);//往下移动110        src.left = 0;   //0,0          src.top = 0;        src.right = 200;        src.bottom = 200;        //扩大图片的显示部分        canvas.drawBitmap(bitmap, src, dst, mPaint);        canvas.translate(0, 210);//往下移动110        //扩大供图片显示的区域        dst.left = 100;            dst.top = 100;            dst.right = 400;          dst.bottom = 400;         canvas.drawBitmap(bitmap, src, dst, mPaint);    }

界面如下所示:
这里写图片描述

此处有一点需要说明,在绘图结束退出Activity的时候,我们需要调用bitmap的recyle()方法,防止内存泄露,本程序在onDestroy()方法中执行了该方法。

drawColor

drawColor(int color):设置画板颜色drawColor(int color, PorterDuff.Mode mode)

drawPicture

drawPicture(Picture picture, RectF dst)drawPicture(Picture picture)drawPicture(Picture picture, Rect dst)

drawPosText

drawPosText(char[] text, int index, int count, float[] pos, Paint paint)drawPosText(String text, float[] pos, Paint paint)

drawVertices

drawVertices(Canvas.VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, Paint paint)

其他

drawPaint(Paint paint):drawVertices(Canvas.VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, Paint paint):

2.clipXXX()方法族:

在当前的画图区域裁剪(clip)出一个新的画图区域,这个画图区域就是canvas 对象的当前画图区域了。比如:clipRect(new Rect()),那么该矩形区域就是canvas的当前画图区域

clipPath(Path path)clipPath(Path path, Region.Op op)clipRect(Rect rect, Region.Op op)clipRect(RectF rect, Region.Op op)clipRect(int left, int top, int right, int bottom)clipRect(float left, float top, float right, float bottom)clipRect(RectF rect)clipRect(float left, float top, float right, float bottom, Region.Op op)clipRect(Rect rect)clipRegion(Region region)clipRegion(Region region, Region.Op op)

3.save()和restore()方法:

saveFlags 说明 ALL_SAVE_FLAG 保存全部的状态 CLIP_SAVE_FLAG 保存裁剪的某个区域的状态 CLIP_TO_LAYER_SAVE_FLAG 保存预先设置的范围里的状态 FULL_COLOR_LAYER_SAVE_FLAG 保存彩色涂层 HAS_ALPHA_LAYER_SAVE_FLAG 不透明图层保存 MATRIX_SAVE_FLAG Matrix信息(translate,rotate,scale,skew)的状态保存

save

save( ):用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作!save(int saveFlags):saveLayer(RectF bounds, Paint paint, int saveFlags):saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags):saveLayerAlpha(RectF bounds, int alpha, int saveFlags):saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int saveFlags):

restore

restore():用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。restoreToCount(int saveCount):

save()和restore()要配对使用(restore可以比save少,但不能多),若restore调用次数比save多,会报错!

4.translate(float dx, float dy):

translate(float dx, float dy):

平移,将画布的坐标原点向左右方向移动x,向上下方向移动y.canvas的默认位置是在(0,0)

5.scale(float sx, float sy):

扩大,x为水平方向的放大倍数,y为竖直方向的放大倍数

scale(float sx, float sy):对画布进行缩放,sx为水平方向缩放比例,sy为竖直方向的缩放比例,px和py我也不知道,小数为缩小, 整数为放大scale(float sx, float sy, float px, float py):

6.rotate(float degrees):

rotate(float degrees):旋转,围绕坐标原点旋转degrees度,值为正顺时针旋转rotate(float degrees, float px, float py):degrees为旋转角度,px和py为指定旋转的中心点坐标(px,py)

7.getXXX()方法族:

getClipBounds():getClipBounds(Rect bounds):getDensity():getDrawFilter():getHeight():getMatrix(Matrix ctm):getMatrix():getMaximumBitmapHeight():getMaximumBitmapWidth():getSaveCount():getWidth():

8.setXXX()方法族:

setBitmap(Bitmap bitmap):setDensity(int density):setDrawFilter(DrawFilter filter):setMatrix(Matrix matrix):

其他:

concat(Matrix matrix):isHardwareAccelerated():isOpaque():quickReject(Path path, Canvas.EdgeType type):quickReject(float left, float top, float right, float bottom, Canvas.EdgeType type):quickReject(RectF rect, Canvas.EdgeType type):skew(float sx, float sy):倾斜,也可以译作斜切,扭曲,sx为x轴方向上倾斜的对应角度,sy为y轴方向上倾斜的对应角度,两个值都是tan值哦! 都是tan值!都是tan值!比如要在x轴方向上倾斜60度,那么小数值对应:tan 60 = 根号3 = 1.732

说明:

  • 画笔Paint控制着所绘制的图形的具体外观,Paint默认的字体大小为12px,在绘制文本时我们往往要考虑密度density设置合适的字体大小。画笔的默认颜色为黑色,默认的style为FILL,默认的cap为BUTT,默认的线宽为0,参见下图所示:
    这里写图片描述

引用:
8.3.1 三个绘图工具类详解 | 菜鸟教程
Android中Canvas绘图基础详解(附源码下载,贝塞尔曲线) - 孙群 - 博客频道 - CSDN.NET
Android 自定义view实现水波纹效果 - 享受技术带来的快乐! - 博客频道 - CSDN.NET
Canvas之translate、scale、rotate、skew方法讲解!,canvasskew_Android教程 | 帮客之家
Andriod中绘(画)图—-Canvas的使用详解 - qinjuning、lets go - 博客频道 - CSDN.NET
Region、RegionIterator——Android 2D Graphics学习(二)、Canvas篇2、Canvas裁剪和Region、RegionIterator - Kilnn - 博客频道 - CSDN.NET
图层——android Graphics(四):canvas变换与操作 - harvic - 博客频道 - CSDN.NET
倒影——android图片处理方法(不断收集中) - 短裤党 - ITeye技术网站
Android闪闪发光字体效果 - xu_fu的专栏 - 博客频道 - CSDN.NET
Region介绍


Android Canvas绘图详解(图文) - yunnywu的专栏 - 博客频道 - CSDN.NET
Android画图Path的使用 - tt_mc - 博客园
Android利用canvas画各种图形(点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形) - 任海丽(3G/移动开发) - 博客频道 - CSDN.NET
Android 动态水波效果 - 开源中国社区

0 0
原创粉丝点击