作为Android Graphics专题的开篇,毫无疑问,我们将讨论Android UI技术的核心概念——Canvas。
Canvas是Android UI框架的基础,在Android的控件体系中,所有容器类、控件类在实现上都依赖于Canvas,界面的绘制实质上都是Canvas绘制的。本文将讨论Canvs的由来,并通过实例展示Canvas的基础用法。
对于应用开发而言,我们可以不去深究Canvas与Android 控件体系的实现细节,但明白Canvas与控件的关联有助于我们更好的使用Canvas。Android控件体系不是了解的朋友可以参见博文《Android原理揭秘系列之View、ViewGroup》。
先看下Android所有控件的基类——View.java的代码片段:
… protected void onDraw(Canvas canvas) { } ….
熟悉Andriod应用开发的人对onDraw方法一定不会陌生。View基类里onDraw方法里是空的,但请注意,方法传入了形参——Canvas对象,也就是说,Canvas对象是UI体系流程中已经创建好的,我们直接拿来用即可,一般不需要自己构造。Canvas的典型使用场景是,在自定义控件重载基类的onDraw方法,并在onDraw方法中通过Canvas绘制我们想要的图形、图片等效果。
我们再看看容器类的基类——ViewGroup.java的dispatchDraw方法的代码片段:
protected void dispatchDraw(Canvas canvas) { … for (int i = 0; i < count; i++) { … more |= drawChild(canvas, child, drawingTime); } … }
dispatchDraw方法是ViewGroup分发绘制子View的核心函数,其通过drawChild方法具体绘制各个子View。这里我们只需要注意Canvas对象的出现位置,同样,Canvas作为形参从dispatchDraw方法传入,并传给drawChild方法用以绘制子view。
通过View和ViewGroup两个核心函数的代码片段分析,我们能够非常清晰的明确Canvas在控件体系中的作用,以及我们接下来将讨论的Canvas用法的canvas对象来自何处。
Canvas在概念上可以理解为其它编程语言中的画布,在画布中,我们可以绘制各种图形,也可以绘制图片,更深层次的,如上ViewGroup的dispatchDraw方法所描述的,我们可以通过变换Canvas,进而在容器内中自定义的绘制子控件。
本文只讨论Canvas的基础用法,即在自定义控件重载的onDraw方法中,使用Canvas来绘制基本的图形、图像等基础用法。
Android的官方SDK中罗列了Canvas的所有API,可点击详细查看。
这里罗列下在实际应用开发中用得非常普遍的几个API:
1) 绘制Bitmap:drawBitmap、drawPicture
2) 绘制颜色:drawColor、drawARGB
3) 绘制基本形状:drawPoint、drawLine、drawCircle、drawArc、drawRect、drawRoundRect
4) 绘制剪切区:drawPath、clipPath、clipRect、clipRegion
5) 变换Canvas:save、restore、translate、scale、rotate、concat(Matrix matrix)、setMatrix(Matrix matrix)
6) 绘制顶点数据:drawVertices、drawBitmapMesh
上面的六项基本概况了Canvas的使用得最普遍的API,各API的具体含义和用法参见SDK。熟练掌握这些API的功能和用法基本可以满足开发需要。下面通过两个代码示例来演示Canvas的基本用法。
示例1:绘制Bitmap
private static class SampleView extends View { private Bitmap mBitmap; private Bitmap mBitmap2; private Bitmap mBitmap3; private Bitmap mBitmap4; public SampleView(Context context) { super(context); setFocusable(true); java.io.InputStream is; is = context.getResources().openRawResource(R.drawable.beach); BitmapFactory.Options opts = new BitmapFactory.Options(); Bitmap bm; opts.inJustDecodeBounds = true; bm = BitmapFactory.decodeStream(is, null, opts); opts.inJustDecodeBounds = false; opts.inSampleSize = 4; bm = BitmapFactory.decodeStream(is, null, opts); mBitmap = bm; //通过配置参数解码生成Bitmap is = context.getResources().openRawResource(R.drawable.frog); mBitmap2 = BitmapFactory.decodeStream(is); //通过打开资源ID直接解码图片 int w = mBitmap2.getWidth(); int h = mBitmap2.getHeight(); int[] pixels = new int[w*h]; mBitmap2.getPixels(pixels, 0, w, 0, 0, w, h); mBitmap3 = Bitmap.createBitmap(pixels, 0, w, w, h, Bitmap.Config.ARGB_8888); //通过缓冲区数据构造Bitmap mBitmap4 = Bitmap.createBitmap(pixels, 0, w, w, h, Bitmap.Config.ARGB_4444); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(0xFFCCCCCC); Paint p = new Paint(); p.setAntiAlias(true); //设置防锯齿 canvas.drawBitmap(mBitmap, 10, 10, null); canvas.drawBitmap(mBitmap2, 10, 170, null); canvas.drawBitmap(mBitmap3, 110, 170, null); canvas.drawBitmap(mBitmap4, 210, 170, null); //通过drawBitmap绘制图 } }
示例1通过继承基类View实现了一个绘制Bitmap的自定义View SampleView,SampleView在构造函数中通过几种不同的方式分别构造了四个不同的Bitmap,在onDraw方法中,通过onDraw方法传入的canvas绘制Bitmap。这样实现的控件在界面上将根据不同的坐标位置绘制出四幅图片的效果。注意,由于Android View的onDraw方法在界面显示、隐藏、遮挡等很多场合都会被系统频繁调用,因此,像构造Bitmap这样的耗费较大内存资源的工资不应该放在onDraw方法中去执行。
示例2:绘制顶点数据实现变形效果
private static class SampleView extends View { private final Paint mPaint = new Paint(); private final float[] mVerts = new float[10]; private final float[] mTexs = new float[10]; private final short[] mIndices = { 0, 1, 2, 3, 4, 1 }; private final Matrix mMatrix = new Matrix(); private final Matrix mInverse = new Matrix(); private static void setXY(float[] array, int index, float x, float y) { array[index*2 + 0] = x; array[index*2 + 1] = y; } public SampleView(Context context) { super(context); setFocusable(true); Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.beach); Shader s = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(s);//通过BitmapShader设置Paint的Shader float w = bm.getWidth(); float h = bm.getHeight(); // construct our mesh setXY(mTexs, 0, w/2, h/2); setXY(mTexs, 1, 0, 0); setXY(mTexs, 2, w, 0); setXY(mTexs, 3, w, h); setXY(mTexs, 4, 0, h); //初始化图片纹理映射坐标 setXY(mVerts, 0, w/2, h/2); setXY(mVerts, 1, 0, 0); setXY(mVerts, 2, w, 0); setXY(mVerts, 3, w, h); setXY(mVerts, 4, 0, h);//初始化顶点数据数组 mMatrix.setScale(0.8f, 0.8f); mMatrix.preTranslate(20, 20); mMatrix.invert(mInverse); //初始化变形矩阵 } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(0xFFCCCCCC); //绘制背景色 canvas.save(); //变形canvas前先保存canvas现场 canvas.concat(mMatrix); //设置变换矩阵 canvas.drawVertices(Canvas.VertexMode.TRIANGLE_FAN, 10, mVerts, 0, mTexs, 0, null, 0, null, 0, 0, mPaint); //绘制当前顶点坐标和纹理坐标决定的图片Paint,得到图片变形效果。 canvas.translate(0, 240);//向下平移canvas canvas.drawVertices(Canvas.VertexMode.TRIANGLE_FAN, 10, mVerts, 0, mTexs, 0, null, 0, mIndices, 0, 6, mPaint); //绘制当前顶点坐标和纹理坐标和索引数组决定的图片Paint,得到另外的图片变形效果。 canvas.restore();//变换完成后恢复canvas现场 } @Override public boolean onTouchEvent(MotionEvent event) { float[] pt = { event.getX(), event.getY() }; mInverse.mapPoints(pt); // 根据当前的触摸位置变换Marix setXY(mVerts, 0, pt[0], pt[1]); //据触摸的位置变换顶点坐标 invalidate();//刷新界面,触发onDraw方法被再次调用 return true; } }
示例2是Canvas的一个比较综合性的用法示例,用到了canvas的多个API,理解了该示例,对Canvas的用法基本就达到了比较熟练的程度。该示例的一些新的概念如Paint、Shader、Matrix等概念后续专题会作有专门的介绍,敬请关注。
示例2有如下一些知识点:
1) 用户的触摸事件在onTouchEvent中传入,通过传入event参数可以获取当前触摸膜的坐标点,并根据这个坐标位置参数来改变算法的相关参数,进而达到根据不同的坐标位置来达到变形的目的。
2) 调用View的invaidate方法可以触发View的重绘流程,重而触发onDraw方法的调用。
3)可以通过canvas的drawColor方法来绘制View的背景色。
4) 由于在onDraw方法中传入的Canvas参数是一个引用,该canvas对象在其他地方还会使用,因此,如果绘制中会改变canvas的几何参数,需要在变换前后采用canvas.save()、canvas.restore()方法对来备份和恢复canvas现场。注意,这两个方法必须成对出现,否则会导致严重的波及问题。
5) 变换canvas的几何参数可以通过concat连接Maxrix矩阵或者translate平移、scalse缩放、rotate旋转等方法来实现。
6)可以通过drawVertices方法绘制具有变形图片的效果,具体变形的样式取决于顶点坐标、纹理坐标、索引数组和Paint设置的BitmapShader。灵活使用该API可以以2D的API实现近似3D的效果。与drawVertices类似的API还有drawBitmapMesh,可以实现基于网格顶点的复杂3D效果。
示例2的运行效果参见下图:
本文是Android Canvas的基础篇,主要讨论Canvas的一些基本概念和常用API,后续文章中将继续涉及Canvas的方方面面。Android Graphics专题的下一篇文章将聚焦Graphics中用得非常普通的概念——Paint,敬请期待。
我的手机专卖店,欢迎各位看官捧场:http://vpclub.octech.com.cn/ztewd/9495.html