Android绘图基础--Canvas和Drawable

Github链接

概述

画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绘制这个方块。

《Android绘图基础--Canvas和Drawable》 CustomView.draw.png

思考一个问题,为什么mCanvas需要设置Bitmap?

很简单,因为它没有持有一块内存地址,自然没法绘制。来看一下draw的起点ViewRootImpl(软件绘制,不开启硬件加速下)。

《Android绘图基础--Canvas和Drawable》 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。

《Android绘图基础--Canvas和Drawable》 SurfaceView.internalLockCanvas.png

顺带一提Canvas.save和Canvas.restore方法,如下Demo

《Android绘图基础--Canvas和Drawable》 MyView.onDraw.png

效果图如

《Android绘图基础--Canvas和Drawable》 效果图.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等。

《Android绘图基础--Canvas和Drawable》 getdrawable.png

从Resource.getDrawable会判断是否.xml结尾,不是的话走6,7步,如果从xml中读取,需要getResource.getDrawable -> ResourceImpl.loadDrawableForCookie -> drawable.createFromXml -> DrawableInflater.inflateFromXmlForDensity -> drawable.inflateFromTag

《Android绘图基础--Canvas和Drawable》 DrawableInflater.inflateFromTag.png

看一下Shape实现类GradientDrawable的inflate实现,读取各项属性并赋值,到draw方法。

《Android绘图基础--Canvas和Drawable》 GradientDrawable.draw.png

调用canvas.drawRect把mRect画出来,而mRect的赋值在ensureValidRect。[图片上传失败…(image-a25af0-1515826613001)]

bounds在哪里设置的?答案是ImageView.updateDrawable内,会调用Drawable.getIntrinsicHeight赋值(从xml中size属性读取),再调用configureBounds -> setBounds,如果使用的不是ImageView,一定要在draw之前调用setBounds,否则size就会出错。

《Android绘图基础--Canvas和Drawable》 ImageView.updateDrawable.png

回到loadDrawableForCookie,再看一下6,7步加载图片的过程,通过AssetManager读取图片流数据,通过Drawable.createFromResourceStream这个我们经常使用的方法获取到Drawable。

《Android绘图基础--Canvas和Drawable》 Drawable.createFromResourcesStream.png

取到屏幕密度之后调用BitmapFactory.decodeResourcesStream,计算密度后调用native创建Bitmap,感兴趣的同学可以看下更具体的分析文章(如理解Bitmap)。

总结

本文探究了两点

  • Canvas能绘制的原因,View.Canvas与new Canvas的区别。
  • 创建Drawable的过程

参考资料

Android 7.1.1 源码

Android 官方文档,Canvas and Drawable, Drawable

    原文作者:豆沙包67
    原文地址: https://www.jianshu.com/p/ebca5649a51d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞