Canvas与Region.Op入门

本文深入探讨了Canvas API的核心方法及其应用场景,包括concat、drawBitmap、drawCircle、getMatrix等,通过实例演示了如何利用这些方法进行图像绘制、坐标变换、形状绘制等操作。此外,还介绍了Canvas的方法如translate、drawRoundRect、drawPoints、drawArc以及高级方法如drawPaint、clipRect等,详细解析了Canvas的坐标系移动、圆角矩形绘制、点绘制、弧线绘制等功能,并提供了丰富的代码示例。

常用方法  

        concat(Matrix):canvas内置矩阵左乘参数矩阵,并将每一个元素除以2,得到的结果做为canvas内置矩阵。
				mCan = new Canvas();
				Matrix m = new Matrix();
				m.setValues(new float[] { 3, 0, 2, 0, 2, 0, 4, 3, 2 });
				Matrix m2 = new Matrix();
				m2.setValues(new float[] { 2, 3, 2, 4, 5, 6, 2, 4, 1 });
				mCan.setMatrix(m2);
				System.out.println("mCan = " + mCan.getMatrix());
				//输出结果为:[2.0, 3.0, 2.0][4.0, 5.0, 6.0][2.0, 4.0, 1.0]——即m2的值
				mCan.concat(m);
				System.out.println("mCan = " + mCan.getMatrix());
				//输出结果为:[7.0, 6.0, 4.0][18.0, 14.0, 10.0][5.0, 5.5, 3.0]

        drawBitmap(Bitmap,Matrix,Paint):画图片。其中Matrix可参考Matrix的使用。bitmap中每一个像素点的坐标可以记为值为x,y,1的3*1列向量,而matrix是3*3的矩阵。原坐标左乘matrix便得到新坐标,计算如下:


其中等号右边的为新坐标。简单点说这里面的matrix就是用来对bitmap进行坐标变换的。

        drawCircle():画圆。除了画bitmap,在画别的图形时一般需要提供一个Paint对象。而Paint可以设置画笔的宽度setStrokeWidth()。因此,可以通过该方法画一个空心圆。注意:在画空心圆时,圆的半径应该是画笔宽度的一半加上内环的半径。具体应用可参考圆形展开

        getMatrix():得到与当前canvas相关联的Matrix对象。在关闭硬件加速的情况下,得到的并不是一个单位矩阵,而是进行平移后的矩阵(平移是为了将当前的canvas移去状态栏,actionbar下面);在不关闭硬件加速时得到的会是一个单位矩阵。因此,想通过matrix对canvas进行操作时,必须通过getMatrix()得到matrix,再对该matrix进行操作。操作完成后通过setMatrix()再设置到相应的canvas中。

        Canvas(Bitmap):Canvas的构造方法中可以传递一个bitmap。如果采用该构造方法,那以后用该Canvas对象所绘的图形都会显示在参数Bitmap上。

        drawColor():为整个canvas绘制颜色,整个画布的颜色,但并不是底色,它会覆盖掉画布上已经绘制出来的图形。

        drawText():画文字。其中参数x,y并不是指的左上角的坐标,而是指的左边和baseline的交叉点。这一点和drawBitmap(Bitmap,float,float,Paint)不同,在drawBitmap()中,第二,三个参数分别指的是Bitmap的左上角的坐标。

		canvas.drawText("gaf", 60, 60, paint);//paint是默认的设置
		canvas.drawLine(0, 0, 60, 60, paint);
		canvas.drawLine(0, 60, 250, 60, paint);
		canvas.drawLine(60,0,60,250,paint);
图为:


        translate():移动。该方法会把画布整个的进行平移,但是已经画上的图画并不是进行移动,只有后画上的图画会在移动后的画布上进行绘制。其余的rotate(),scale()也是一样,分别表示旋转(顺时针为正,逆时针为负)和缩放。

canvas.drawColor(0xFFCCCCCC);
canvas.drawLine(0, 100, 500, 100, mPaint);
canvas.translate(0, 100);
canvas.drawLine(0, 100, 500, 100, mPaint);
图为:


        从上图可以看出同样的drawLine代码,但画出的线并没有重合。这是因为canves垂直移动了100,所以后画的线虽然在y轴上的位置还是100,但是实际上是200了(canvas本身移动了100)。

        drawRoundRect():圆角矩形。圆角处的弧线是椭圆的一部分。该椭圆的x,y半轴分别是drawRoundRect()中的第二个,第三个参数。

		RectF rect = new RectF(50,50,1000,1000);
		canvas.drawRoundRect(rect, 20, 50, paint);
		paint.setColor(Color.BLUE);
		RectF oval = new RectF(50, 50, 90, 150);
		canvas.drawOval(oval , paint);
图为:

        drawPoints():画点。该方法会把数组中的点都画出来。由于pts[2*x]与pts[2*x+1]表示一个点的x,y轴坐标,所以pts的长度必定要是偶数。

float[] pts = new float[200];
		for(int x = 0 ;x<100;x+=1){
			pts[2*x] = x;
			pts[2*x+1]= x;
		}
		canvas.drawPoints(pts, paint);
图为一条直接

        drawArc():画弧线。有一个重载方法drawArc(RectF,float,float,boolean,Paint),参数分别表示:圆弧所属的矩形(对于矩形来说,一定有一个内接椭圆,而圆弧就是该内接椭圆中的一部分);从内接椭圆中开始截取的角度;圆弧所扫过的角度,沿x轴正方向为0度,顺时针旋转为正;是否使用圆心,如果使用圆心,则该该圆弧类似于扇形。具体效果可见apidemo中的graphics-arcs。

		boolean useCenter = false;
		RectF oval = new RectF(50, 50, 1000, 1000);
		float startAngle = 0;
		float sweepAngle = 50;
		canvas.drawArc(oval , startAngle, sweepAngle, useCenter , paint);
图为(其中左边第四个参数为false,右边为true):

  

        drawPaint():等效于在canvas中用参数paint画一个无限大的矩形(但是drawPaint()方法要比画矩形执行的快)。

官方说明:

        Fill the entire canvas' bitmap (restricted to the current clip) with the specified paint. This is equivalent (but faster) to drawing an infinitely large rectangle with the specified paint.   

        clipRect():将指定的参数从canvas中挖出,只有在该区域中绘制的才显示。

        canvas.clipRect(100, 100, 200, 200);//绘制一个矩形,之后的canvas就是该矩形
        canvas.drawColor(Color.WHITE);   //为该矩形设置颜色
        p.addCircle(100, 100, 50,Path.Direction.CCW);//为Path对象p添加圆形(最后一个参数指的是逆时针)
        paint.setColor(Color.RED);
        canvas.drawPath(p, paint);//在canvas上绘制圆形

从上面的图形可以看出:超出矩形区域的圆没有显示。

        drawBitmap(Bitmap,Rect,Rect,Paint):画bitmap。第一个参数是源bitmap,第二个参数是源bitmap中的某一个区域,会把该区域给截取出来,然后放到第三个参数指定的区域中。如果两个区域大小不一样,会自动进行缩放。要注意:这里只是绘制源bitmap中的某一个区域,并不是所有的都绘制上。

		RectF dst = new RectF(50, 100, 400, 950);
		Rect src = new Rect(500, 500, 750, 750);
		canvas.drawBitmap(bitmap, src , dst , paint);
		canvas.drawBitmap(bitmap,src,src,paint);
		RectF dst1 = new RectF(950, 1000, 1000, 1150);
		canvas.drawBitmap(bitmap,src,dst1,paint);
图为

        drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint):画bitmap,用该方法可以实现对图片的扭曲,也可以实现水面落叶效果。

        它的原理为:假想在bitmap上有一份网格(横,竖轴上的格数由第二个,第三个参数决定)。由于网格的格数固定,各个交叉点的原始坐标就随之固定。当将这些交叉点的坐标移动到新坐标(各个交叉点的新坐标便是由第四个参数决定,它是一维数据——偶数表示x轴坐标,奇数表示y轴坐标)时,图片便会发生扭曲。

        由于横、竖的网格数由第二,三个参数决定(四线三格中的三格),那么就必然有第二个参数+1,第三个参数+1条横 、竖线,所以有交点(第二个参数+1)*(第三个参数+1),所以共有数字(第二个参数+1)*(第三个参数+1)*2(每个点有x,y两个值),故第四个参数最低的长度是(第二个参数+1)*(第三个参数+1)*2(每个点有x,y两个值)

        第五个参数表示verts从哪个开始为有效的。

        第六个参数colors表示每一点的颜色值,通常为null。

        第七个参数colorOffset表示colors从哪个开始为有效值,通常为0。

两个常用的配套方法:

        方法一:初始化第四个参数

		private static final int WIDTH = 20;// 水平格数
		private static final int HEIGHT = 20;// 垂直格数
		private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);// 共有的点数
		private final float[] mVerts = new float[COUNT * 2];
		private final float[] mOrig = new float[COUNT * 2];

		private void init() {
			int index = 0;
			float w = mBitmap.getWidth();// 要进行扭曲的bitmap
			float h = mBitmap.getHeight();
			for (int y = 0; y <= HEIGHT; y++) {
				float fy = h * y / HEIGHT;
				for (int x = 0; x <= WIDTH; x++) {
					float fx = w * x / WIDTH;
					setXY(mVerts, index, fx, fy);//初始时交叉点的新坐标与原坐标相同
					setXY(mOrig, index, fx, fy);//未扭曲之前各个交叉点的坐标
					index += 1;
				}
			}
		}

		private static void setXY(float[] array, int index, float x, float y) {
			array[index * 2 + 0] = x;
			array[index * 2 + 1] = y;
		}

        方法二:触摸时进行扭曲

		private void warp(float cx, float cy) {// 在onTouch方法中调用,可以不断的重新计算扭曲度
			final float K = 10000;
			float[] src = mOrig;
			float[] dst = mVerts;
			for (int i = 0; i < COUNT * 2; i += 2) {// 注意这里是直接加2,也就是直接换到下一个点
				float x = src[i + 0];
				float y = src[i + 1];
				float dx = cx - x;
				float dy = cy - y;
				float dd = dx * dx + dy * dy;
				float d = FloatMath.sqrt(dd);
				float pull = K / (dd + 0.000001f);
				pull /= (d + 0.000001f);
				// android.util.Log.d("skia", "index " + i + " dist=" + d +
				// " pull=" + pull);
				if (pull >= 1) {// 离的越近,该值越大
					dst[i + 0] = cx;
					dst[i + 1] = cy;
				} else {
					dst[i + 0] = x + dx * pull;
					dst[i + 1] = y + dy * pull;
				}
			}
		}

        save()与restore():保存与恢复。恢复时会恢复到保存之前的状态,而保存与恢复中间进行的操作都不会作用于恢复后。

canvas.rotate(-30);//代码一
		canvas.save();<strong>//保存画布原来的状态(也就是逆时针旋转30度)</strong>
		canvas.rotate(30);//代码二
		canvas.drawText("测试", 100, 100, paint);
		canvas.restore();<strong>//恢复到画布被保存时的状态,save()与restore()方法间的绘制都会进行相应的变形</strong>
		canvas.drawBitmap(bitmap, 100, 100, paint);//恢复到保存前的状态,所以图画会逆时针旋转30度
图为:


        在代码一处整个canvas旋转了30度,而代码二处又逆向旋转了30度,所以"测试"二字是正常的。但是在restore()后,代码二处的逆向旋转已经作用不到了,所以bitmap是倾斜的。

        saveLayer():将指定的矩形区域保存成一个图层,在该图层中绘制完成后系统自动将图层上的图形和原图层整合。与save()类似,都是通过restore()恢复到原图层。利用saveLayer()可以实现一些不规则的样式

        skew():斜切。参数值指的是斜切角度的正切值。如skew(1,0)则在x轴上倾斜的角度搂45度,因为tan45=1。

        drawPicture():绘制一个Picture。Picture参考。简单点理解就是:Picture就是一幅画好的图片,然后可以将该图片绘制在Canvas中。

        drawPosText():类似drawText(),参数中float数组用于指定各个字符的位置。可以实现分散对齐效果。要注意的是必须指定每一个字符的位置,如果不正常就直接崩掉。

Region.Op

        Canvas.clipRect()还可以传入参数:Region.Op。主要用来控制两个clip的协作模式,使用该类时一定要关闭硬件加速 参考:http://jimiaotong.blog.163.com/blog/static/189502520119188032938/

        DIFFERENCE:“挖去”原画布中的一块区域。即:旧画布减去新旧两块画布的交集。

        canvas.clipRect(100, 100, 200, 200);//代码一
        canvas.drawColor(Color.WHITE);   
        p.addCircle(100, 100, 50,Path.Direction.CCW);
        paint.setColor(Color.RED);
        canvas.drawPath(p, paint);
        canvas.clipRect(110, 110, 150, 150,Region.Op.DIFFERENCE);//代码二
        paint.setColor(Color.YELLOW);
        canvas.drawRect(105, 105, 140, 140, paint);//代码三

        在代码一处clip了整个白色区域,而代码二处将其中的(110,110,150,150)这块给挖掉,因此在代码三时只有很少一部分黄色(大部分都在被挖掉的区域中,不会在显示旧的canvas中,所以露出了原来的红色)。

        REPLACE:“替换”画布区域。不论新旧两块canvas的相交情况,新canvas一定完全显示;相交的部分新的会覆盖掉旧的。如果两个canvas不相交,那各自都会完全显示。

        UNION “合并”画布区域,和原来的画布结合成一个整块的画布,后面绘制的图形都将在两个画布上进行显示。即:新旧两块画布的并集

	super.onDraw(canvas);
        canvas.clipRect(100, 100, 120, 120);
        canvas.clipRect(125, 125, 145, 145,Region.Op.UNION);//和原来的画布结合成一个整块的画布,以后绘制的图形都将在两个画布上进行显示
        paint.setColor(Color.RED);
        canvas.drawRect(100, 100, 150, 150, paint);

结果图:


        XOR 表示“合并”后“挖去”重叠的画布区域,剩余的部分才是新的画布,即:新旧两块画布的并集减去两块画布的交集

	super.onDraw(canvas);
        canvas.clipRect(100, 100, 130, 130);
        canvas.clipRect(120, 120, 145, 145,Region.Op.XOR);//和原来的画布结合成一个整块的画布,并将重合的部分给除掉
        paint.setColor(Color.RED);
        canvas.drawRect(100, 100, 150, 150, paint);
效果图


        REVERSE_DIFFERENCE:表示“反向挖去”画布区域,与DIFFERENCE刚好相反。在新的画布区域中“挖去”两块画片的交集

	super.onDraw(canvas);
        canvas.clipRect(100, 100, 130, 130);
        canvas.clipRect(120, 120, 145, 145,Region.Op.REVERSE_DIFFERENCE);//和原来的画布结合成一个整块的画布,并将重合的部分给除掉
        paint.setColor(Color.RED);
        canvas.drawRect(100, 100, 150, 150, paint)
结果图


        缺的那一角是因为它处理旧画布(100,100,130,130)区域中,没有被新画布保留。

        Region.Op.INTERSECT 表示“交集”,即在新的画布和当前画布的共同区域(代码略)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值