图形绘制简介
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、一个用来保存像素的Bitmap2、一个或多个画笔Paint3、需要绘制的内容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。
小细节上图,最初始的情况是栈里只有 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
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")@Overrideprotected 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);//*************************************************************************************
//画路径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.LEFTcanvas.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 calledCanvas.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);}
@Overrideprotected 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.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);// 这个圆是在原来的图层上划的, 没有透明度}}