概述
画 2D 图形有两种方法:
把图片和动画设置到布局文件的 View 里,整个绘图过程由系统的视图树处理,我们只需要定义好图形.适合于不需要动态改变的简单图形,如静态图片和定义好的动画
在 Canvas 上绘图.在类的 ondraw 方法内获取 Canvas,或者调用 Canvas.drawXXX 方法.适合重复重绘自己的图形,如视频.既可以通过自定义 View 通过 invalidate,在 UI 线程里回调 onDraw 方法,也可以通过 SurfaceView 启动别的线程调用 invalidate.
用 Canvas 绘图
Canvas 实际上是封装了各种 draw 方法的类,调用 draw 方法把图形绘制到底层的 Surface 上,即绘制在 Window 上.
在 onDraw 方法里使用 Canvas 绘制比较简单,不再赘述.
在 SurfaceView 通过 lockCanvas 获取 Canvas,同上.
自定义 Canvas.创建 Canvas 时,需要设置 Bitmap.Bitmap 中有内存指针 mNativePtr 和属性,可以简单视为一小块内存.从 前篇 知道,绘制其实是写一块共享内存,而 Bitmap 可视为共享内存的一小块.
这个例子中构造了两个 Canvas 和一个 Bitmap,分别调用其 draw 方法,先是 mCanvas 往 Bitmap 里绘制一个方块,再在 onDraw 方法内调用 canvas.drawBitmap 绘制这个方块.
CustomView.draw.png
思考一个问题,为什么 mCanvas 需要设置 Bitmap?
很简单,因为它没有持有一块内存地址,自然没法绘制.来看一下 draw 的起点 ViewRootImpl(软件绘制,不开启硬件加速下).
ViewRootImpl.drawSoftware.png
这个通过 mSurface.lockCanvas 返回的 Canvas 是 View.draw 的 canvas 变量,所以当 1,2 情况时,Canvas 都持有一个 Bitmap,指向共享内存里的某一小块,当调用 Canvas.draw 方法时就能绘制出东西.但对于自定义 Canvas 来说并不是,即使设置一个 Bitmap 和绘制了 Bitmap,但不往共享内存上写,屏幕上是不会显示的,SurfaceView 同理,通过 Surface.lockCanvas 获取持有共享内存的 Canvas,绘制完毕后调用 Surface.unlockCanvasAndPost 把绘制内容显示到 surface 上并 release 掉 Canvas.
SurfaceView.internalLockCanvas.png
顺带一提 Canvas.save 和 Canvas.restore 方法,如下 Demo
MyView.onDraw.png
效果图如
效果图. png
画的是三个颜色和旋转角度都不同的小方形.
步骤 1 把默认坐标系旋转 20°,画出第一个蓝色的方形,步骤 2 保存当前的 matrix(旋转了 20°),继续旋转 20°,此时坐标系已经旋转了 40°,画出第二个黄色的方块,步骤 3,恢复上一步保存的 matrix(旋转了 20°),此时坐标系还是旋转了 20°,步骤 4,再旋转 40°,此时坐标系旋转了 60°,画出第三个黑色方块.
Canvas.save 用于保存当前 matrix 和 clip,Canvas.restore 用于恢复上次保存的 matrix 和 clip.
Drawable
Drawable 是一个能画出来的物体的抽象,使用前需要调用 setBounds 确定位置和大小,通过 getIntrinsicHeight 和 getIntrinsicWidth 取到实际大小.Drawable 可以有几种形式存在:Bitmap,Nine Patch,Vector,Shape,Layers 等.
getdrawable.png
从 Resource.getDrawable 会判断是否. xml 结尾,不是的话走 6,7 步,如果从 xml 中读取,需要 getResource.getDrawable -> ResourceImpl.loadDrawableForCookie -> drawable.createFromXml -> DrawableInflater.inflateFromXmlForDensity -> drawable.inflateFromTag
DrawableInflater.inflateFromTag.png
看一下 Shape 实现类 GradientDrawable 的 inflate 实现,读取各项属性并赋值,到 draw 方法.
GradientDrawable.draw.png
调用 canvas.drawRect 把 mRect 画出来,而 mRect 的赋值在 ensureValidRect.[图片上传失败...(image-a25af0-1515826613001)]
bounds 在哪里设置的?答案是 ImageView.updateDrawable 内,会调用 Drawable.getIntrinsicHeight 赋值(从 xml 中 size 属性读取),再调用 configureBounds -> setBounds,如果使用的不是 ImageView,一定要在 draw 之前调用 setBounds,否则 size 就会出错.
ImageView.updateDrawable.png
回到 loadDrawableForCookie,再看一下 6,7 步加载图片的过程,通过 AssetManager 读取图片流数据,通过 Drawable.createFromResourceStream 这个我们经常使用的方法获取到 Drawable.
Drawable.createFromResourcesStream.png
取到屏幕密度之后调用 BitmapFactory.decodeResourcesStream,计算密度后调用 native 创建 Bitmap,感兴趣的同学可以看下更具体的分析文章(如 理解 Bitmap ).
总结
本文探究了两点
Canvas 能绘制的原因,View.Canvas 与 new Canvas 的区别.
创建 Drawable 的过程
参考资料
Android 7.1.1 源码
Android 官方文档, Canvas and Drawable , Drawable 等
来源: http://www.jianshu.com/p/ebca5649a51d