图形绘制 Canvas Paint Path 详解


图形绘制简介
       Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0。大部分2D使用的api都在android.graphics和android.graphics.drawable包中。他们提供了图形处理相关的Canvas、ColorFilter、Point、RetcF等类,还有一些动画相关的AnimationDrawable、BitmapDrawable、TransitionDrawable等。
      以图形处理来说,我们最常用到的就是在一个View上画一些图片、形状或者自定义的文本内容,这些都是使用Canvas来实现的。
      另外,我们可以获取View中的Canvas对象,在绘制一些内容后调用View.invalidate方法让View重新刷新,然后再绘制一个新的内容,以此多次之后,就实现了2D动画的效果。

画图需要四大基本要素:
1、一个用来保存像素的Bitmap
2、一个或多个画笔Paint
3、需要绘制的内容
4、一个Canvas画布,用来在Bitmap上使用Paint绘制内容

Canvas对象的获取方式
Canvas对象的获取方式有三种:
1、通过重写View.onDraw方法获取Canvas对象。
这种方式根据环境还分为两种:一种是普通View的Canvas,还有一种是SurfaceView的Canvas。
两种的主要是区别就是,可以在SurfaceView中定义一个专门的线程来完成画图工作,应用程序不需要等待View的刷图,提高性能。
前面一种适合处理量比较小,帧率比较低的动画方面的绘图,比如说象棋游戏之类的;而后一种主要用在游戏或高品质动画方面的绘图。
2、直接创建一个Canvas对象:
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);  
Canvas c = new Canvas(bitmap);
上面代码创建了一个尺寸是100*100的Bitmap,使用它作为Canvas操作的对象,这时候的Canvas就是使用创建的方式。
当你使用创建的Canvas在bitmap上执行绘制方法后,你还可以将绘制的结果提交给另外一个Canvas,这样就可以达到两个Canvas协作完成的效果,简化逻辑。
          但是android SDK建议使用View.onDraw参数里提供的Canvas就好,没必要自己创建一个新的Canvas对象。
3、调用SurfaceHolder.lockCanvas()也会返回一个Canvas对象,可以在 surfaceView 或 TextureView中使用。

Canvas位置装换、保存、恢复
       Android还提供了一些对Canvas位置转换的方法:rorate、scale、translate、skew(扭曲)等,而且它允许你通过getMatrix获得它的转换矩阵对象,并可以直接操作它。这些操作就像是虽然你的笔还是在原来的地方画,但是画纸(坐标原点)旋转或者移动了,所以你画的东西的方位就产生了变化。
      为了方便使用这些转换操作,Canvas 还提供了保存和回滚属性的方法save和restore。save用来保存Canvas的状态,save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。restore用来恢复Canvas之前保存的状态,防止save后对Canvas执行的操作对后续的绘制有影响。注意:save和restore要配对使用,如果restore调用次数比save多,会引发Error。

小细节
图形绘制 Canvas Paint Path 详解
上图,最初始的情况是栈里只有 0 1 2 3 4 ,然后执行 save 方法两次,则 5 和 6 出现在栈中

Canvas可绘制的图形种类
1、绘制背景(填充)
      drawARGB(int a, int r, int g, int b)
      drawColor(int color)
      drawRGB(int r, int g, int b)
      drawColor(int color, PorterDuff.Mode mode)
2、绘制几何图形
     canvas.drawArc (扇形,弓形,弧线区域)
     canvas.drawCircle(圆)
     canvas.drawOval(圆和椭圆)
     canvas.drawLine(线)
     canvas.drawPoint(点)
     canvas.drawRect(矩形)
     canvas.drawRoundRect(圆角矩形)
     canvas.drawVertices(顶点)
     cnavas.drawPath(路径,可用来绘制任意图形)
3、绘制图片
       canvas.drawBitmap (位图)
       canvas.drawPicture (图片)
4、绘制文本
       canvas.drawText
图形绘制 Canvas Paint Path 详解  图形绘制 Canvas Paint Path 详解 

Paint画笔工具相关设置
Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色、样式等绘制信息,指定了如何绘制文本和图形。画笔对象有很多设置方法, 大体上可以分为两类:
1、图形相关的设置方法
  • setARGB(int a,int r,int g,int b); 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
  • setAlpha(int a); 设置绘制图形的透明度。
  • setColor(int color); 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。
  • setAntiAlias(boolean aa); 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
  • setDither(boolean dither); 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
  • setFilterBitmap(boolean filter); 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示速度,本设置项依赖于dither和xfermode的设置
  • setMaskFilter(MaskFilter maskfilter); 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等
  • setColorFilter(ColorFilter colorfilter); 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
  • setPathEffect(PathEffect effect); 设置绘制路径的效果,如点画线等
  • setShader(Shader shader); 设置图像效果,使用Shader可以绘制出各种渐变效果
  • setShadowLayer(float radius ,float dx,float dy,int color); 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
  • setStyle(Paint.Style style); 设置画笔的样式。FILL:实心,默认;FILL_OR_STROKE:填充并设置描边;STROKE:描边,空心
  • setStrokeCap(Paint.Cap cap); 定义线段断点形状。当画笔样式为STROKE或FILL_OR_STROKE时,设置我们的画笔在【离开】画板时候留下的最后一点图形,如圆形样式 Cap.ROUND(有延长),或方形样式Cap.BUTT(默认,没有延长)与Cap.SQUARE(有延长)
  • setSrokeJoin(Paint.Join join); 设置绘制时各图形的结合方式(图形节点的样式),如平滑效果等
  • setStrokeWidth(float width); 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细
  • setXfermode(Xfermode xfermode); 设置图形重叠时的处理方式,如合并,取交集或并集
2.文本相关的设置方法
  • setFakeBoldText(boolean fakeBoldText); 模拟实现粗体文字,设置在小字体上效果会比较差
  • setSubpixelText(boolean subpixelText); 设置该项为true,将有助于文本在LCD屏幕上的显示效果
  • setTextAlign(Paint.Align align); 设置绘制文字的对齐方向
  • setTextScaleX(float scaleX); 设置绘制文字x轴的缩放比例,可以实现文字拉伸的效果
  • setTextSize(float textSize); 设置绘制文字的字号大小
  • setTextSkewX(float skewX); 设置斜体文字,skewX为x轴方向的倾斜弧度
  • setTypeface(Typeface typeface); 设置字体风格,包括粗体,斜体以及衬线体,非衬线体等
  • setUnderlineText(boolean underlineText); 设置带有下划线的文字效果
  • setStrikeThruText(boolean strikeThruText); 设置带有删除线的效果

代码-绘制各种图形
public class TestPaintView extends View {
    private Context context;
    private Paint paint;
    private RectF rect;//Rect是使用int类型作为数值,RectF是使用float类型作为数值
    private Path path;//主要用于绘制复杂的图形轮廓,比如折线,圆弧以及各种复杂图案
    private Bitmap bitmap;
    private int left, top, right, bottom;

    public TestPaintView(Context context) {
        super(context);
        this.context = context;
        paint = new Paint();
        rect = new RectF(0, 0, 0, 0);//左X、上Y、右X、下Y相应的距离,即左上角、右下角的坐标,系统不会检查数值的有效性
        paint.setStrokeJoin(Paint.Join.ROUND);//设置绘制时图形的结合方式(图形节点的样式)
        paint.setStrokeCap(Paint.Cap.ROUND);//设置画笔在【离开】画板时留下的最后一点的样式
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStrokeWidth(dp2px(0.5f));//设置画笔粗细,单位为像素
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        //背景颜色
        canvas.drawColor(0x33005500);
        //矩形
        initRect(10, 50, 10, 50);
        paint.setStyle(Paint.Style.STROKE);//描边(空心),默认是FILL(实心、填充)
        canvas.drawRect(rect, paint);
        //绘制弧线区域(扇形或弓形)
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(0xff00ff00);
        canvas.drawArc(rect, 0, 90, false, paint);//当为false时是一个不经过【圆心】的弓形,当为true时是一个经过圆心的扇形
        paint.setColor(0xffff0000);
        canvas.drawArc(rect, 90, 150, true, paint); //圆弧所在矩形,起始角度90,旋转角度150,顺时针为正
        //矩形内切圆(或椭圆)
        paint.setStyle(Paint.Style.STROKE);
        initRect(10, 30, 10, 50);
        canvas.drawRect(rect, paint);
        paint.setARGB(255, 0, 0, 255);
        canvas.drawOval(rect, paint);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawArc(rect, 0, 90, false, paint); //椭圆的扇形
        paint.setARGB(128, 255, 0, 255);
        canvas.drawArc(rect.left, rect.top, rect.right, rect.bottom, 90f, 150f, true, paint);

        //圆角矩形
        paint.setStyle(Paint.Style.STROKE);
        initRect(10, 50, 10, 50);
        canvas.drawRoundRect(rect, 20, 20, paint);//矩形,两侧圆角弧度的大小,一般都设为相同
        //画直线
        canvas.drawLine(dp2px(120), dp2px(25), dp2px(120), dp2px(55), paint);// 画一条线(首尾两点的坐标)
        paint.setColor(Color.BLUE);
        float[] points = new float[] { dp2px(120), dp2px(15), dp2px(150), dp2px(15),//画多条线。每条线都需要两个坐标(每两个值组成一个坐标)
                dp2px(130), dp2px(25), dp2px(150), dp2px(55), dp2px(150), dp2px(25) };
        canvas.drawLines(points, paint);//最后两个值不够组成一条线,所以被废弃掉了。
        paint.setColor(Color.BLACK);
        canvas.drawLines(points, 2, 8, paint);//指定跳过前2个数据,取出8个数据绘制直线
        // 画圆  
        canvas.drawCircle(dp2px(190), dp2px(35), dp2px(25), paint);//圆心坐标,半径
        //画点
        paint.setStrokeWidth(dp2px(3));
        canvas.drawPoint(dp2px(190), dp2px(35), paint);//画一个点
        paint.setColor(Color.RED);
        canvas.drawPoints(points, paint);//画多个点,每两个值组成一个坐标
        //画图片,就是贴图  
        canvas.drawBitmap(bitmap, dp2px(220), dp2px(10), paint);//坐标指的是左上角的位置
        //canvas.drawCircle(320 + bitmap.getWidth() / 2, 200 + bitmap.getWidth() / 2, bitmap.getWidth() / 2, paint);
        //*************************************************************************************
图形绘制 Canvas Paint Path 详解
        //画路径1,不规则封闭图形
        paint.setStrokeWidth(dp2px(0.5f));
        path = new Path();
        path.moveTo(dp2px(10), dp2px(70));//指定初始轮廓点,若没指定默认从(0,0)点开始
        path.lineTo(dp2px(10), dp2px(100));//从当前轮廓点绘制一条线段到指定轮廓点
        path.lineTo(dp2px(50), dp2px(100));
        path.lineTo(dp2px(50), dp2px(80));
        path.close(); // 回到初始点形成封闭的曲线
        canvas.drawPath(path, paint);
        //画路径2,利用path也可以画各种图形,api使用上和canvas有些许区别
        rect = new RectF(dp2px(60), dp2px(70), dp2px(110), dp2px(120));
        path.addRect(rect, Direction.CW);//利用path画矩形。Diection.CW 顺时针方向,Diection.CCW 逆时针方向
        canvas.drawPath(path, paint);
        paint.setColor(Color.BLUE);//注意,同一path绘制的图形的颜色一定是相同的
        Path path2 = new Path();//如果这里不重新new一个path,则前面用path绘制的图形的颜色也都会变
        path2.addRoundRect(rect, new float[] { 20, 60, 20, 60, 60, 60, 20, 20 }, Direction.CW);//从左上角顺时针开始,四个角的x轴y轴方向的弧度
        canvas.drawPath(path2, paint);
        //绘制文本
        canvas.drawLine(rect.left, rect.centerY(), rect.right, rect.centerY(), paint);
        paint.reset();//重置
        paint.setTextSize(dp2px(12));//单位是px,只在绘制文字时有效
        paint.setTextAlign(Align.CENTER);//绘制的文字以drawText时指定的 float x 水平居中,默认值是Align.LEFT
        canvas.drawText("1efg", rect.centerX(), rect.centerY(), paint);//注意 float y 代表的是 baseline 的值,也即e和f的下边界,而非g的下边界
        paint.setUnderlineText(true);//带下划线
        canvas.drawText("包青天efg", 0, "包青天efg".length(), rect.right + dp2px(30), rect.top + dp2px(12), paint);//(+textSize)可实现和矩形顶部对齐
        canvas.drawText(new char[] { 'J', '!', '。', '.' }, 0, 4, rect.right + dp2px(30), rect.centerY(), paint);
        //在指定路径上绘制文本
        Path path3 = new Path();
        path3.moveTo(rect.right, rect.bottom);
        path3.lineTo(rect.right + dp2px(150), rect.centerY());
        canvas.drawPath(path3, paint);
        paint.setTextAlign(Align.LEFT);
        canvas.drawTextOnPath("123456789", path3, 30, -10, paint);//float hOffset,相对基准线的向右偏移值, float vOffset向下偏移值
    }

    /**
     * 重新设置矩形边界
     * @param leftAdd, 代表新矩形的左边界距离上一个矩形的右边界的距离
     * @param width,代表矩形的宽度
     * @param topAdd, 代表新矩形的上边界的值
     * @param hight,代表矩形的高度
     */
    private void initRect(int leftAdd, int width, int topValue, int hight) {
        left = right + leftAdd;
        right = left + width;
        top = topValue;
        bottom = top + hight;
        rect.left = dp2px(left);
        rect.right = dp2px(right);
        rect.top = dp2px(top);
        rect.bottom = dp2px(bottom);
    }
    private int dp2px(float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

代码-位置装换、保存、恢复
public class TestCanvasView extends View {
    private static final int SAVE_FLAGS = //Canvas.ALL_SAVE_FLAG //= 0x1F,还原所有,restore everything when restore() is called 
    Canvas.MATRIX_SAVE_FLAG//= 0x01,需要还原Matrix。restore the current matrix when restore() is called 
            | Canvas.CLIP_SAVE_FLAG //= 0x02,需要还原Clip。restore the current clip when restore() is called 
            | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG //=0x04, 图层的 clip 标记。the layer needs to per-pixel alpha 
            | Canvas.FULL_COLOR_LAYER_SAVE_FLAG//= 0x08,图层的 color 标记。the layer needs to 8-bits per color component
            | Canvas.CLIP_TO_LAYER_SAVE_FLAG;// = 0x10,图层的 clip 标记,在saveLayer 和 saveLayerAlpha时Android强烈建议加上他
    private Paint mPaint;
    public TestCanvasView(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        //画一个红色的圆
        mPaint.setColor(Color.RED);
        canvas.drawCircle(100, 100, 100, mPaint);
        canvas.save();//保存Canvas的状态,save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。save()默认保存的是matrix和clip
        //画一个绿色的拉长的圆(即椭圆)
        canvas.scale(0.5f, 1);//x、y轴的缩放比例
        mPaint.setColor(Color.GREEN);
        canvas.drawCircle(100, 100, 100, mPaint);
        //画一个黄色的矩形
        canvas.restore();//restore用来恢复Canvas之前保存的状态,防止save后对Canvas执行的指定操作对后续的绘制有影响
        canvas.translate(200, 0);//把画布平移。此操作对下面新建的那个图层依然是有效的
        mPaint.setColor(Color.YELLOW);
        canvas.drawRect(0, 0, 200, 200, mPaint);
        canvas.save(SAVE_FLAGS);//指定哪些需要还原。只有指定matrix或clip才有效,其余几个参数在saveLayer和saveLayerAlpha方法中才有效
图形绘制 Canvas Paint Path 详解
        //画一个蓝色的扭曲的矩形
        canvas.skew(2f, 1);//图形变换唯一规则:将x坐标全部变为*2,将y坐标全部变为*1。至于是否平行啦之类的都不去限制。
        mPaint.setColor(Color.BLUE);
        canvas.drawRect(0, 0, 100, 100, mPaint);
        //画一个带透明度的蓝色的旋转的小矩形
        canvas.restore();//取消扭曲。
        canvas.translate(200, 0);
        canvas.rotate(30, 50, 50);//沿指定点为中心顺时针旋转指定角度。默认是以左上角为中心
        int count = canvas.saveLayerAlpha(0, 0, 200, 200, 0x88, SAVE_FLAGS);//在指定边界新建一个透明度为0x88的图层
        canvas.drawRect(0, 0, 100, 100, mPaint);//绘制时使用的颜色仍是上次的蓝色,而非save之前的颜色。这个图层的透明度对此图形是有影响的
        //画一个绿色的小圆
        canvas.restore();
        canvas.restore();//如果restore调用的次数大于save的调用次数,会出错。
        canvas.restoreToCount(count);
        mPaint.setColor(Color.GREEN);
        canvas.drawCircle(50, 50, 50, mPaint);// 这个圆是在原来的图层上划的, 没有透明度
    }
}

上一篇:Kubernetes Serverless 框架的全面对比OpennFaas、OpenWhisk、Fission、Kubeless


下一篇:2021-04-26