代码改变世界

Android隐喻(三) 图形绘制: Canvas、SurfaceView、Paint、Surface、SurfaceHolder、Bitmap

2012-07-04 15:14  CreateLight  阅读(9320)  评论(1编辑  收藏  举报

  如果你是画家,有一群人想要看你的画,想象一下整个流程。

  首先找一块画布,执笔,绘制,完成后找一面墙把画挂上去,众人围观。其中不可缺少的要素包括:画布、(画家拿笔)绘制、挂墙展示。对于计算机,也就对应着 帧缓存、像素填充、刷新至屏幕。如果你希望显示一些东西,那么首先你需要拿到一块缓存,然后向这块缓存中填充像素(也就是绘制),然后将这块缓存交给屏幕显示出来。

   Android中的图形绘制,不外如是。

  来看一下典型的绘制-显示代码:

class GameView extends SurfaceView implements Callback
{
  @Override
  public void surfaceCreated(SurfaceHolder holder)
  {
    Canvas c = holder.lockCanvas();
    c.drawBitmap(x, y, bitmap, paint);
    holder.unlockCanvasAndPost(c);
  }
}

   这里先谈谈Canvas,其英文语意为"画布“,一开始天真的我就真把它当成了画布。所谓画布,乃承载画的载体,之于计算机,我的第一反应:画布 = buffer = 缓存,用像素填充画布,然后将它”贴“到屏幕上,就完成了绘制和显示的过程。正是由于我有这个先入为主的观念,按我看来,Canvas应该有个形如Canvas(width, height, PixelType)构造方法,用来生成一块width * height * getBytesOfPerPixel(PixelType) 字节大小的buffer,然后我就可以尽情在上面进行绘制了。但是Canvas最常见的构造方法是Canvas(Bitmap),这曾令我百思不得其解,把一个位图给一块画布,这是什么意思?这个错误的隐喻导致我在看Canvas和Bitmap时纠结了很久。

  事实上,Canvas为一个功能类,提供了大量的drawXXX方法,所以它实际上扮演的是画家的角色,而非画布。要想绘画,你必须给画家一块画布,而Canvas(Bitmap)中的Bitmap则充当了画布的角色。

  再来看上面那个典型的绘制-显示代码:我们需要从中找到绘制(画家和画笔)、缓存(画布)、刷新至屏幕(挂墙展示)这三个基本要素。

  1,绘制

    和一开始的隐喻对应,画家是Canvas,画笔是Paint类的实例paint,Canvas使用paint完成绘制。即代码:c.drawBitmap(x, y, bitmap, paint); 本质是将bitmap绘制(复制)在画布上。

  2, 画布

    来看看画布在哪,Canvas来自于holder.lockCanvas(),这个方法会通过jni调用对应的native方法,其本质是从holder所持有的Surface中获取屏幕冲区的地址,然后用这个地址构造一个native的Bitmap对象(SKBitmap),再用这个Bitmap对象构造一个native的Canvas对象(SKCanvas),再返回这个Canvas对象,java层的Canvas对象其实只是对SKCanvas对象的一个简单包装,所有绘制方法都是转交给SKCanvas来做。

   所以这里的画布就是由屏幕缓存所构造出来的Bitmap。holder.lockCanvas()所返回的Canvas对象是在这块画布上进行绘制。这里的Surface是对屏幕缓存的抽象。我们绘制,可以在屏幕缓存上直接绘制,也可以先绘制在任何一块缓存上,然后将这块缓存中的内容复制到屏幕缓存中。

   值得注意的是,这里你并没有直接获得一块画布(Bitmap),而只是获得了一个画家(Canvas),画家出现的时候手里已经有了画布,不需要你提供给他,就可以进行绘制。

   3,刷新至屏幕

    对应的代码为holder.unlockCanvasAndPost(c),其本质就是将之前绘制好的画布交给图形显示驱动,在真实的设备(屏幕)上显示出来。

    总结: 如果你想在Android上进行绘制,你首先要找到一块缓存(Bitmap),然后将其交给画家(构造Canvas),让画家在其上绘制(drawXXX,可能需要提供一只Paint画笔),再将其交给屏幕去显示(本质上是将 所绘制的缓存复制到屏幕缓存中,再触发更新命令,所以你需要找到一个Surface(屏幕缓存),而Android中你无法直接构造Surface,你只能通过SurfaceView或其它方式由系统提供给你一个Surface,而SurfaceView中的Surface是在WindowManagerImpl.addView() - 即添加窗口到WMS时创建的,这个Surface对象为ViewRoot所持有。)

  Andoid中除了用SurfaceView自己来控制屏幕绘制外,对于简单应用,更通常的做法是使用XML指定的View布局,通过框架回调来实现屏幕显示,但其本质和SurvaceView并无区别,同样是 获取缓存、绘制、显示在屏幕,后续将进行分析。