Android绘图Canvas笔记

Posted 炎之铠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android绘图Canvas笔记相关的知识,希望对你有一定的参考价值。

  Canvas的翻译是画布,android系统里面的的2D绘图用的就是它。对应Canvas,官方的解释是这样的:

The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

  也就是说,要实现画图,有4个基础要素:一个可以存放像素的Bitmap、提供画图方法的Canvas,想要绘画的内容,实现绘画的画笔paint。

在Android4.0的一些设备上,打开硬件加速时使用自定义View可能会出现一些问题,所以测试的时候要先关闭硬件加速再运行,具体可以看这里

  对于Canvas的练习我做了一个Demo:


demo的github地址

Canvas对象获取

  1. 直接创建对象:Canvas canvas = new Canvas(),创建时还可以传入一个Bitmap对象,这样Canvas所画的东西就会在这个Bitmap上。
  2. 通过重写View.onDraw方法,在这个方法里可以获得这个View对应的Canvas对象:
    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        //在这里获取Canvas对象
    
  1. 在SurfaceView里画图时,从SurfaceView的surfaceHolder里锁定获取Canvas,Canvas操作结束之后解锁并执行Canvas
        SurfaceView surfaceView = new SurfaceView(this);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        Canvas c = surfaceHolder.lockCanvas();//获取Canvas

        //Canvas操作...

        surfaceHolder.unlockCanvasAndPost(c);

官方推荐是用后面的两种方法获取Canvas的,由于SurfaceView里面有一条专门的线程用来画图,所以第3种方法创建的Canvas画图性能最好,用于高质量的、刷新频率高的图形,而第2种方法刷新频率没有那么快,但是系统花销小。

画笔Paint

  工欲善其事,必先利其器,没有画笔怎么画图呢,下面所说到的很多Canvas.drawXX()方法都要输入一个Paint对象作为参数。Paint有以下的常用方法:

Paint.setStyle(Style style)     //设置画笔风格

  Style有3种类型:Paint.Style.FILL_AND_STROKE,Paint.Style.FILL,Paint.Style.STROKE,一种是实心有边,一种是实心无边,一种是空心,前两种就相差一条边,如果边细的话是看不出分别的,边粗一点的话就相当于加粗效果。

Paint.setAntiAlias(boolean aa)      //设置画笔是否抗锯齿(边缘柔化、消除混叠) 
Paint.setTextSize(float textSize)       //设置字体尺寸。 
Paint.setTextAlign(Align align)         //设置坐标点相对字体的位置:Paint.Align.Left,Paint.Align.CENTER,Paint.Align.Right

setSubpixelText(boolean subpixelText)      //对文本设置亚像素,把每个像素细分,利用插值的方法把细分后的小块填满,总的来说就是让文本更加清晰,系统消耗更大
setUnderlineText(boolean underlineText)      //设置文本的下划线
setStrikeThruText(boolean strikeThruText)    //设置文本的删除线
setFakeBoldText(boolean fakeBoldText)       //设置文本粗体
Paint.setStrokeWidth(float width)       //设置画笔的粗细
Paint.setColor(int color)       //设置画笔颜色,可以直接引入Color类,如Color.red等
Paint.setARGB(int a, int r, int g, int b)      //设置画笔的a,r,p,g值
Paint.setAlpha(int a)       //设置Alpha值 
Paint.getColor()        //得到画笔的颜色 
Paint.getAlpha()        //得到画笔的Alpha值。
Paint.setShader(Shader shader)      //设置Shader

  利用Shader可以画出很多很好看的图形,这篇文章我觉得写的不错,可以参考参考。下面是一个线性渐变器的示例效果:

Paint.setStrokeCap(Cap cap)     //设置画笔的Cap效果:Paint.Cap.BUTT,Paint.Cap.ROUND,Paint.Cap.SQUARE

  Cap直译是帽子,盖子,在这里大概的效果就是我们的画笔在下笔时和笔离开画板时的图形(就像毛笔的拖尾一样),默认的Cap是Paint.Cap.BUTT,就相当于没有效果。下面是3种Cap的效果对比(用分别设置了3种Cap画很粗的线,黑色部分就相当于不设置Cap时候画的线,黑色加红色就是线的全部):

Paint.setStrokeJoin(Join join)      //设置画笔的Join效果:Paint.Join.MITER,Paint.Join.ROUN,Paint.Join.BEVEL

  这里的Join的效果就是当两条边有交叉的时候的效果,默认是Paint.Join.MITER,就相当于没效果,下面是3种Join的效果对比:

注意,使用Join效果要把画笔的style设置为Paint.Style.STROKE或者Paint.Style.FILL_AND_STROKE,因为是设置边(Stroke)的Join,边(Stroke)都没有的FILL是不会产生这个效果的,至于前面的Cap,因为是画线,肯定要用边,如果画图形的话用FILL也会没效果的。

参考
http://blog.csdn.net/abcdef314159/article/details/51720686

利用Canvas绘画

  Canvas提供了一系列方法来让我们使用来画出图形,下图可以大概地看一下:

图片出自这里

   下面是具体的方法介绍:

Canvas.drawXX()方法

  通过Canvas.drawXX()系列方法可以画出一些基本的图:

改变画布颜色

public void drawColor(@ColorInt int color)     

  可以通过这个方法传入一个Color类的常量参数来设置整个画布的颜色。

画点

public void drawPoint(float x, float y, @NonNull Paint paint)
public void drawPointsfloat[] pts, int offset, int count,@NonNull Paint paint)
public void drawPoints(@NonNull float[] pts, @NonNull Paint paint)

  参数简单,不用解释,看名字就懂,第一个方法是画一个点,后面两个画一组点。

画直线

public void drawLine(float startX, float startY, float stopX, float stopY,@NonNull Paint paint)
public void drawLines(float[] pts, int offset, int count, Paint paint)
public void drawLines(@NonNull float[] pts, @NonNull Paint paint)

  参数简单,不用解释,看名字就懂。

画矩形

public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)

public void drawRect(@NonNull Rect r, @NonNull Paint paint)

public void drawRect(@NonNull RectF rect, @NonNull Paint paint)

  画矩形使用的是drawRect()方法,第一种方法前四个参数分别是矩形左上角和右下角的点的想x,y坐标。第二三种是传入一个矩阵类——Rect是参数为int的矩阵,Rect是参数为float的矩阵。

        //画实心矩形
        canvas.drawRect(80,80,300,200, paintF);

画圆

public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

  这里的参数也很简单,分别是圆心的x,y坐标和半径,画笔。

        //画实心圆
        canvas.drawCircle(190,350,100,paintF);

画椭圆

public void drawOval(@NonNull RectF oval, @NonNull Paint paint)

//这个方法需要API21以上才适用
public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint)

  这里传入的参数和矩形一样,实际上椭圆就是根据传入矩形的长宽作为长轴和短轴画的,下面的实例说明了画的椭圆就是所给参数矩形的内切椭圆。

        //画实心椭圆
        canvas.drawOval(new RectF(80,500,300,650),paintF);
        //画和椭圆一样参数的空心矩形
        canvas.drawRect(new RectF(80,500,300,650),paintS);

画弧

drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)

//这个方法需要API21以上才适用
public void drawArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean useCenter, @NonNull Paint paint)

  弧其实就是从椭圆截取的一部分,一个矩形可以确定一个椭圆,这里也是,传入一个矩形的参数,然后后面的两个参数分别是开始截取的角度和要截取多大的角度(注意这里的角度是从x轴的正方向以顺时针方向开始算的),还有后面一个Boolean型参数是选择是否连接圆心,选择ture的话画出来的图形就是一个扇形,false的话就是一个弓形

        //画扇形
        canvas.drawArc(new RectF(80,850,300,1070),-120,110,true,paintF);
        //画弓形
        canvas.drawArc(new Rect(80,1000,300,1220),-120,110,false,paintF);

画圆角矩形

  圆角矩形就是用四段与角的两边相切的圆弧替换原来的四只角的矩形。

drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)

//这个方法需要API21以上才适用
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,@NonNull Paint paint)

  所以这里除了一开始就要传入矩形的参数之外,后面还要分别传入弧形的半长轴和半短轴的长度参数

        //画实心圆角矩形
        canvas.drawRoundRect(new RectF(80,700,300,830),30,30,paintF);

写字

  通过Canvas的drawText()方法可以在Canvas上的指定位置写出想要的字

public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

  第一个参数为要写的内容,后面是要写的字出现的坐标位置(如果画笔没设置过setTextAlign()方法的话默认字是在坐标点的右侧 ),最后是传入画笔。

  除此之外,还可以指定每一个字的位置:

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

  但是这两个方法在API16以上的版本弃用了,官方原因是:This method was deprecated in API level 16. This method does not support glyph composition and decomposition and should therefore not be used to render complex scripts. It also doesn’t handle supplementary characters (eg emoji).
  翻译过来好像就是这个方法不支持字形的组合和拆解,不应该应用于呈现复杂的文本,也不支持后面补充的字符,如emoji。。。(我也不是很懂,而且官方又没给出可以用什么代替)

  还可以在一条特定的路径Path上写字:

        path.cubicTo(240,750,440,450,640,600);
        canvas.drawTextOnPath("在Path上写的字11111111112222",path,50,0,paintTFAS);
        //画出Path
        canvas.drawPath(path,paintTS);


  

改变画笔的属性可以写出不同形式的字

  除了基本的:

public native void setColor(@ColorInt int color)    //设置颜色
public native void setAlpha(int a)          //设置透明度
public native void setTextSize(float textSize)      //设置字体大小
public native void setUnderlineText(boolean underlineText)      //设置字体下划线

  还有使用三种不同的style写的字:

  还有阴影文字:

        paintSL.setShadowLayer(5,5,5,Color.YELLOW);
        canvas.drawText("阴影文字",40,450,paintSL);


  斜体字:

        paintX.setTextSkewX(-0.5f);
        canvas.drawText("斜体文字",0,200,40,530,paintX);

Path的使用

  Path也就是路径,写字有笔画,画画当然也有路径顺序,设置好路径之后(Path只是用于描述一个区域,单单使用Path是不能产生任何效果的),我们可以通过Canvas.drawPath()方法来把它画出来,也可以通过Canvas.drawTextOnPath()方法在路径上写字,也可以通过Canvas.clipPath()方法把Path所包含的区域裁剪出来。
  下面介绍Path类的用法,使用Path首先要new一个Path对象,然后就默认建立了一个起点是在Canvas坐标为(0,0)的Path,如果想要移动当前的起点,可以:

    moveTo(float x, float y)        //移动这个点,把这个点当作起点

所谓路径,就是通过一个一个的点连起来的,所以可以用:

    lineTo(float x, float y)        //连接这个点,然后后面的会从这里开始画,但不是把这里当作起点

把点连起来成了线,如果想把路径闭合成为一个图形,就可以:

    close()     //将当前点和起点连在一起

还可以加入图形,注意下面的方法都是会改变起点的

addArc(RectF oval, float startAngle, float sweepAngle)      //加入弧线路径,具体参数和Canvas.drawArc()方法的参数差不多
addCircle(float x, float y, float radius, Path.Direction dir)   //加入圆形路径,起点都是在x轴正方向的0度,其中第dir参数用来指定绘制时是顺时针还是逆时针:CW为顺时针,CCW为逆时针
addOval(RectF oval, Path.Direction dir)     //加入椭圆形路径,其中 oval作为椭圆的外切矩形区域
addRect(RectF rect, Path.Direction dir)     //加入矩形路径
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)      //加入圆角矩形路径

如下面的代码:

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        Path path = new Path();
        canvas.translate(400,500);
        path.lineTo(-100,0);
        path.addCircle(0,0,100, Path.Direction.CCW);
        path.lineTo(-100,200);
        path.lineTo(200,200);
        path.close();
        canvas.drawPath(path,paintS);
    

我先把Canvas的坐标系移到屏幕中部的位置,然后开始操作:一开始起点是(0,0),连接点(-100,0),然后以原点为中心画一个半径100像素的圆,这时候起点就改变了,变成(0,100),所以后面close()方法连起来的是这个起点。

在使用Path的时候尽量不要在onDraw()方法里new对象,因为这样一旦View频繁刷新的话系统就会频繁创建对象,拖慢刷新速度。建一个全局的Path对象,在onDraw()方法按需要修改就好了。
  这里先介绍下Path的简单用法,具体深入的可以了解这里

贝赛尔曲线

  贝塞尔曲线是是应用非常广泛的数学曲线,基本上任何一条曲线都可以用贝塞尔曲线表示出来,所以很多绘图工具上的钢笔工具都有应用到贝塞尔曲线。
  贝塞尔曲线可以通过一个起点和一个终点,还有若干个控制点来描述(有n个控制点的贝塞尔曲线被称作n+1阶贝塞尔曲线)。Canvas提供了画二阶和三阶贝塞尔曲线的方法(一阶就是一条直线,高阶可以拆解为多条低阶曲线),下面是具体方法:


quadTo(float x1, float y1, float x2, float y2)
//绘制二阶贝塞尔曲线,其中 (x1,y1)为控制点,(x2,y2)为终点
rQuadTo(float x1, float y1, float x2, float y2)
//绘制二阶贝塞尔曲线,其中 (x1,y1)为控制点距离起点的偏移量,(x2,y2)为终点距离起点的偏移量

cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
//绘制三阶贝塞尔曲线,其中(x1,y1),(x2,y2)为控制点,(x3,y3)为终点
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
//绘制三阶贝塞尔曲线,其中(x1,y1),(x2,y2)为控制点距离起点的偏移量,(x3,y3)为终点距离起点的偏移量

  这里只简单介绍一下贝塞尔曲线,想详细理解可以参考这篇文章
  我参考文章里面的圆切线拟合做了一个demo(文章里面的计算方法是只在第二象限的,我把它扩展到4个象限),demo地址可看我的文章头部或者尾部。

Path的特效

  画出来的Path还可以加上一些特效动画,具体可以参考这里

操纵画布Canvas

  前面说了的都是在Canvas上面画图,其实我们还可以直接操作画布来让我们更方便地画图(注意:直接操作画布的意思就是把画布的性质改变了,改变之后的任何操作都会受到这个改变的影响):

平移方法translate

  前面使用Canvas.drawXX()方法的时候都是基于左上角的原点坐标系来定位的,然而这个坐标系原点其实是可以移动的:

public void translate(float dx, float dy)   //将画布原点向右移dx像素,向下移dy像素

  下面用translate方法方便地画出了一个”五环”:

                canvas.translate(150,500);
                canvas.drawCircle(0,0,100, paintS);
                canvas.translate(105,105);
                canvas.drawCircle(0,0,100, paintS);
                canvas.translate(105,-105);
                canvas.drawCircle(0,0,100, paintS);
                canvas.translate(105,105);
                canvas.drawCircle(0,0,100, paintS);
                canvas.translate(105,-105);
                canvas.drawCircle(0,0,100, paintS);

缩放方法scale

public final void scale(float sx, float sy, float px, float py)     //以(px,py)点为中心在x方向缩放sx倍,在y方向缩放sy倍

  就相当于把东西画下去了再放大,所以放的很大的时候会有明显的锯齿,例子:

                canvas.drawCircle(400,780,20, paintF);
                for (int i = 0; i < 11;i++)
                    canvas.drawArc(new RectF(350,750,450,850),-120,60,false,paintS);
                    canvas.scale(1.3f,1.3f,400,800);
                

旋转方法rotate

public final void rotate(float degrees, float px, float py)     //以(px,py)点为中心旋转degrees度

  注意度数是在x轴正方向为0度,按顺时针方向增加的,不同于我们平时的逆时针方向。例子:

                canvas.drawCircle(380,500,300,paintS);
                canvas.drawCircle(380,500,250,paintS);
                int rTimes = 6;
                for (int i = 0; i < rTimes;i++)
                    canvas.rotate(360/rTimes,380,500);
                    canvas.drawLine(380,500,380,200,paintF);
                

错切方法skew

    public void skew(float sx, float sy)    //将画布在x,y方向上倾斜相应的角度,sx,sy为倾斜角度的tan值

  和斜体字差不多,sx大于0代表向左斜,sy大于0代表向上斜,下面这个就是向下斜

                canvas.drawRect(20,20,200,100,paintS);
                canvas.skew(0,1);
//                canvas.drawCircle(150,300,150,paintG);
                canvas.drawRect(20,20,200,100,paintG);

裁剪方法clip

  裁剪就是从画布上裁剪一块下来,剪下来之后其他的区域都不能编辑了,就只能编辑剪下来这一块,裁剪方法有3种:

//裁剪矩形Rect
public boolean clipRect(float left, float top, float right, float bottom,
            @NonNull Region.Op op) 
public boolean clipRect(float left, float top, float right, float bottom)
public boolean clipRect(int left, int top, int right, int bottom)

//裁剪路径Path
public boolean clipPath(@NonNull Path path, @NonNull Region.Op op)
public boolean clipPath(@NonNull Path path)

//裁剪区域Region
public boolean clipRegion(@NonNull Region region, @NonNull Region.Op op)
public boolean clipRegion(@NonNull Region region)

  剪一块东西下来很简单,不用示例了,但是上面三个方法里面都有参数Region.Op就是应用在剪下多个区域下来的情况,当这些区域有重叠的时候,这个参数决定重叠部分该如何处理,多次裁剪之后究竟获得了哪个区域,有以下几种参数:

  • Region.Op.DIFFERENCE是第一次不同于第二次的部分显示出来:
                //裁剪
                canvas.clipRect(0,0,300,300);
                //一次裁剪后的区域(红色)
                canvas.drawColor(Color.RED);
                //画图
                Path path = new Path();
                path.addCircle(300,150,150, Path.Direction.CW);
                canvas.clipPath(path, Region.Op.DIFFERENCE);
                //二次裁剪后获得的区域(绿色)
                canvas.drawColor(Color.GREEN);


- Region.Op.REPLACE,是显示第二次的(代码就是上面的OP参数改为REPLACE):

  • Region.Op.REVERSE_DIFFERENCE,是第二次不同于第一次的部分显示(代码就是上面的OP参数改为REVERSE_DIFFERENCE):

  • Region.Op.INTERSECT,交集显示,显示重叠的区域(代码就是上面的OP参数改为INTERSECT):

  • Region.Op.UNION,联合显示,两次裁剪的区域都要,加起来(代码就是上面的OP参数改为UNION):

  • Region.Op.XOR,异或显示,就是全部的减去重叠部分,剩余的部分显示(代码就是上面的OP参数改为XOR):

保存与恢复方法save、restore

  我们可以把Canvas看作PS里面的图层,因为我们每次采用上面的几个操作Canvas的方法之后,对之后的操作都会有影响。如果我希望用完translate()方法画完一些图之后,Canvas的原点恢复回位,再进行下一步操作,可以在使用translate方法之前先使用save()方法,然后用完translate()方法画完图之后使用restore()方法恢复画布到使用save()方法时的状态,然后进行下一步操作。

                //保存
                canvas.save();
                canvas.translate(300,300);
                canvas.drawCircle(0,0,100,paintF);
                //恢复
                canvas.restore();
                canvas.drawRect(0,0,200,200,paintF);


可以看到矩形是以左上角原点为基准的。

其实save()方法就是把Canvas的当前状态信息入栈,然后restore()方法就是把栈里面的信息出栈,取代当前的Canvas信息,以此达到保存的效果。

综合练习

  首先是做了个坐标系,在View里面获取屏幕像素然后根据这个像素画出了一个xy坐标系(下面是关键部分代码):

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



    //画坐标轴和箭头
    private void drawAxis(Canvas canvas)
        canvas.drawLine(20,20,screenWidth-20,20, paint1);
        canvas.drawLine(20,20,20,screenHeight-20, paint1);
        Path pathX = new Path();
        pathX.moveTo(screenWidth,20);
        pathX.lineTo(screenWidth-20,30);
        pathX.lineTo(screenWidth-20,10);
        pathX.close();
        canvas.drawPath(pathX, paint1);
        Path pathY = new Path();
        pathY.moveTo(20,screenHeight );
        pathY.lineTo(30,screenHeight - 20 );
        pathY.lineTo(10,screenHeight - 20 );
        pathY.close();
        canvas.drawPath(pathY, paint1);

    

    //画刻度
    private void drawScale(Canvas canvas)
        int lengthX = screenWidth - 40;
        int lengthY = screenHeight - 40 ;
        canvas.drawText("0",5,20, paint2);
        for (int i = 20; i < lengthX; i += 20)
            if (i % 100 == 0)
                canvas.drawText(Integer.valueOf(i).toString(),i+5,20, paint2);
                if (i % 200 == 0)
                    canvas.drawLine(20+i,20,20+i,50, paint1);
                else 
                    canvas.drawLine(20+i,20,20+i,40, paint1);
                
            else 
                canvas.drawLine(20+i,20,20+i,30, paint1);
            
        
        for (int i = 20; i < lengthY; i += 20)
            if (i % 100 == 0)
                canvas.drawText(Integer.valueOf(i).toString(),5,i+8+20, paint2);
                if (i % 200 == 0)
                    canvas.drawLine(20,20+i,50,20+i, paint1);
                else 
                    canvas.drawLine(20,20+i,40,20+i, paint1);
                
            else 
                canvas.drawLine(20,20+i,30,20+i, paint1);
            
        
    

    //获取屏幕宽高分辨率方法2
    private void getScreenPixel2(Context context)
        DisplayMetrics dm ;
        dm = getResources().getDisplayMetrics();
        float density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
        int densityDPI = dm.densityDpi; // 屏幕密度(每寸像素:120/160/240/320)
        float xdpi = dm.xdpi;
        float ydpi = dm.ydpi;
        System.out.println("density:"+density+",densityDPI:"+densityDPI);
        System.out.println("screen2dp:"+xdpi+","+ ydpi);
        screenWidth = dm.widthPixels; // 屏幕宽,单位为像素
        screenHeight = dm.heightPixels; // 屏幕高,单位为像素
        System.out.println("screen2:"+screenWidth+","+ screenHeight);
    

  然后我还弄了一个比较科幻的六角形背景,两个组合起来之后发现风格差异太大,只能一个一个地看了,下面实现了触摸移动的效果,但是因为使用View的绘制效率比较低,效果做出来很卡(下面是关键部分代码):

    public int sexSide = 70;
    private float sexHeight = (float) (sexSide*Math.sin(Math.PI*60/180));
    private Path sexPath;
    private  float moveX = 0,moveY = 0,downX = 0,downY = 0;

   private void init() 

        paintSide = new Paint();
        paintSide.setStyle(Paint.Style.STROKE);
        paintSide.setStrokeWidth(5);
        int[] colors = new int[]Color.GREEN,Color.BLUE;
        //设置颜色线性渐变器
        LinearGradient linearGradient = new LinearGradient(0,0,screenWidth/2,screenHeight/2,colors
                ,null, Shader.TileMode.MIRROR);
        paintSide.setShader(linearGradient);

        sexPath = new Path();
        xCount = screenWidth/(sexSide*2)+1;
        yCount = (int) (screenHeight/(sexHeight*2)*2)+1;
    

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        canvas.drawColor(Color.DKGRAY);
        canvas.translate(moveX,moveY);
        //从y方向画
        for (int n = -1; n <= yCount;n++)
            canvas.save();
            if (n % 2 == 1 || n % 2 == -1) 
                canvas.translate((float) (1.5 * sexSide), sexHeight * n);
            else 
                canvas.translate(0, sexHeight * n);
            
            //从x方向画
            for (int i = -1;i < xCount;i++) 
                addSexAnglePath(sexSide+sexSide * i *3, (int) sexHeight);
            
            canvas.drawPath(sexPath,paintSide);
            canvas.restore();
        

    

    //画六角形
    private void addSexAnglePath(int x,int y)
        sexPath.moveTo(-sexSide + x,y);
        sexPath.lineTo(-sexSide/2 + x, -sexHeight + y);
        sexPath.lineTo(sexSide/2 + x,-sexHeight+ y);
        sexPath.lineTo(sexSide + x,y);
        sexPath.lineTo(sexSide/2 + x,sexHeight+ y);
        sexPath.lineTo(-sexSide/2 + x, sexHeight+ y);
        sexPath.close();

    

    //触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        switch (event.getAction())
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                break;
            case MotionEvent.ACTION_UP:
                moveX = (event.getX() - downX) % sexSide;
                moveY = (event.getY() - downY) % sexHeight;
                invalidate();
                break;
        
        return true;
    

总结

  要学会使用Canvas并不难,但是想要利用它做出好看的画面就需要好的创意和强大的数学能力了。
demo的github地址

以上是关于Android绘图Canvas笔记的主要内容,如果未能解决你的问题,请参考以下文章

Android绘图之Canvas变换(6)

android绘图之Canvas基础(2)

canvas 鼠标绘图

Android UICanvas 画布 ⑨ ( Canvas 绘图坐标系平移实例 )

读书笔记 - js高级程序设计 - 第十五章 使用Canvas绘图

HTML5自学笔记[ 14 ]canvas绘图基础2