Android绘图Canvas笔记_android canvas画笔粗细-程序员宅基地

技术标签: canvas  android  Android笔记  

  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地址

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/totond/article/details/58722446

智能推荐

java.sql.SQLException: The server time zone value ‘�й���׼ʱ��‘ is unrecognized or represents more tha_java.sql.sqlexception : the server time zone value-程序员宅基地

文章浏览阅读221次。Springboot项目启动报了一下错误java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more s_java.sql.sqlexception : the server time zone value 'pdt' is unrecognized or

windows程序设计 王艳平版_windows程序设计王艳平-程序员宅基地

文章浏览阅读1.3k次。// 02CreateProcess.cpp文件#include "stdafx.h"#include #include int main(int argc, char* argv[]){char szCommandLine[] = "cmd";STARTUPINFO si = { sizeof(si) };PROCESS_INFORMATION_windows程序设计王艳平

因特网(Internet)和广域网(WAN)之间的区别_wan和internet区别-程序员宅基地

文章浏览阅读3k次。Internet最早来源于美国国防部高级研究计划局DARPA(Defense advanced Research Projects Agency)的前身ARPA建立的ARPAnet,该网于1969年投入使用。最初,ARPAnet主要用于军事研究目的,它有五大特点:1.支持资源共享;2.采用分布式控制技术;3.采用分组交换技术;4.使用通信控制处理机;5.采用分层的网络通信协议。近十年来,随着社会科技,文化和经济的发展,特别是计算机网络技术和通信技术的大发展,随着人类社会从工业社会向信息社会过渡的趋势越来越明_wan和internet区别

重定位表的添加/编辑/删除工具_增加重定位表-程序员宅基地

文章浏览阅读764次。下载地址:http://download.csdn.net/detail/yes2/9519832_增加重定位表

Windows Dll 动态加载_windows动态加载dll-程序员宅基地

文章浏览阅读1.3k次。Windows DLL 动态加载1. 起因2. 问题原因3. 解决3.1 C++ 版3.2 C#版1. 起因写代码的时候,需要动态加载DLL,这是一个非常非常常规的操作对吧。然而在开发的时候表现的一切正常,但是打包后,在别的机子上死活加载不到dll。????2. 问题原因对于这个问题常见的原因无非就以下几个:DLL生成的有问题加载路径写的不对程序的权限不够我这里遇到的是权限不够,所以我以管理员权限运行程序,我以为我终于要解决问题的了,but给我返回了个126(无法找到指定模块)…苦思冥_windows动态加载dll

The selected directory is not a valid home for Go SDK GOROOT_goland 2019.2.3 兼容go什么版本-程序员宅基地

文章浏览阅读448次,点赞3次,收藏2次。Goland2019.2.3 版本 安装 Go1.17.2后需要设置GOROOT路径,选择sdk时报错:The selected directory is not a valid home for Go SDK网上查了一圈都没有解决后重新安装了Go1.16.9版本,重新设置,解决!应该是Go1.17.2 Goland没有兼容吧,不知道什么时候可以解决........._goland 2019.2.3 兼容go什么版本

随便推点

IntellIJ IDEA 配置 Maven修改中央仓库为阿里云仓库_ieda将默认的maven仓库改为阿里的maven仓库-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏20次。IntellIJ IDEA 配置 Maven修改中央仓库修改中央仓库为国内阿里云仓库Idea 自带了apache maven,默认使用的是内置maven,所以我们可以配置全局setting,来调整一下配置,比如远程仓库地址,本地编译环境变量等。1.打开Settings,在输入框输入maven,如图:2.如果本地设置了MAVEN_OPTS 系统环境变量,这个步骤可以忽略。MAVEN_OP..._ieda将默认的maven仓库改为阿里的maven仓库

2021-10-17_microg签名不一致-程序员宅基地

文章浏览阅读5.8k次,点赞2次,收藏4次。HarmonyOS 2.0正式版上安装谷歌服务框架GMS与谷歌应用商店Google Play。无需降级直接安装谷歌框架华为P50 P40 P30 Mate40 Mate30荣耀v30,参考YouTube视频: 华为手机鸿蒙2.0系统HarmonyOS 2.0正式版上安装谷歌服务框架GMS。问题一在激活设备管理器时,出现“由于另一个应用目前正显示在这些选项之上, “设置”无法回应迩的操作”。解决方法:关闭导航悬浮窗、备忘录速记。问题二Googlefier安装MiCrOG - HUAWEI FIX _microg签名不一致

阿里巴巴常考面试题目-程序员宅基地

文章浏览阅读132次。转自: http://blog.csdn.net/yuexianchang/article/details/72835932一、String,StringBuffer, StringBuilder 的区别是什么?String为什么是不可变的?答: 1、String是字符串常量,StringBuffer和StringBuilder都是字符串变量。后两者的字符内容可变,而前者创建后内容不可变。2、...

java 循环删除ftp_java连接FTP、删除、下载文件的方法-程序员宅基地

文章浏览阅读247次。本文共例举了二个连接FTP的方法,可以下载删除FTP上的文件,代码有点凌乱JAVA自带的方法importjava.io.BufferedInputStream;importjava.io.DataInputStream;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava..._java怎么杀掉ftp进程

windows运行基于Node.js搭建的web服务器程序的方法_web服务器的npm run start-程序员宅基地

文章浏览阅读474次。安装Node.js下载:https://nodejs.org/en/下载好.msi文件后,打开安装,按照提示进行即可。安装项目依赖绝大多数下载好了的Node.js项目,只有作者本人写的项目源码,但并没有项目依赖的各种其他文件。所以在运行之前需要把这些依赖的文件下载下来才行。方法是:用任意一款命令行程序(cmd、powershell、windows terminal、cmde..._web服务器的npm run start

嵌入式linux的开发流程_结合课程实验简述嵌入式linux系统的主要开发流程-程序员宅基地

文章浏览阅读1.1k次。嵌入式linux开发流程:1.搭建前期的开发环境,例如搭建linux主机环境,交叉编译器的安装,还有一些常用的开发辅助软件如source insight等等。2.学习对硬件的直接操作,这里和单片机操作类似,我们需要查看原理图还有数据手册对相应的寄存器进行操作,实现功能。这里只需要学会简单的IO,中断,定时器,串口就可以,后面的存储器,IIC,LCD,AD,DA等可以大致了解一下,需要用到时候着重学习。3.学会UBOOT的移植,这里不需要自己去编写,我们需要了解uboot的结构,并且对他进._结合课程实验简述嵌入式linux系统的主要开发流程