天天看点

【Android】の基础——自定义View和SurfaceViewView的绘制过程View绘制的工具类:Canvas、Paint、BitmapSurfaceViewView和SurfaceView比较

  • View的绘制过程
    • measure过程
    • layout过程
    • draw过程
    • 常见的方法
  • View绘制的工具类CanvasPaintBitmap
  • SurfaceView
    • 使用SurfaceView的过程
    • 绘图原理
    • SurfaceView双缓存
  • View和SurfaceView比较

View的绘制过程

View的绘制流程主要由三部分组成,依次是:measure,layout,draw。

measure过程

测量View宽高的过程。

为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的实际宽高都是由父控件和本身决定的。

从ViewRoot开始调用measure()方法去计算View树大小,然后回调子View(ViewGroup)的onMeasure()方法,以此类推:

1. onMeasure()中通过setMeasuredDimension()方法去设置实际的高 (mMeasuredHeight)和宽(mMeasureWidth) 。

2. 2.如果是ViewGroup的话,还要遍历所有子View进行measure()过程,这通过measureChildWithMargins()方法实现,它其实就是调用了每个子View(或ViewGroup)的measure()方法。

一般在进行自定义View开发时,需要复写此方法自定义测量过程。

如果不复写此onMeasure方法,则默认使用getDefaultSize方法得到的值。

在View测量时会传入一个变量MeasureSpec,它的作用就是告诉View应该以哪一种模式测量这个View,SpecMode有三种模式:

  • UNSPECIFIED:表示父容器不对View有任何限制,这种模式主要用于系统内部多次Measure的情况,不需要过多关注
  • AT_MOST:父容器已经指定了大小,View的大小不能大于这个值,相当于布局中使用的wrap_content模式
  • EXACTLY:表示View已经定义了精确的大小,使用这个指定的精确大小specSize作为该View的大小,相当于布局中我们指定了66dp这种精确数值或者match_parent模式

layout过程

确定View在父容器的位置的过程。

根据子视图的大小以及布局参数将View树放到合适的位置上:从host.layout()开始View树的布局,然后回调View/ViewGroup类中的layout()方法。

1. 1.layout方法会设置该View位于父View的坐标轴(即mLeft,mTop,mLeft,mBottom,通过setFrame() )接下来回调onLayout()方法。

2. 如果是ViewGroup类型,需要遍历每个childView(子View或ViewGroup),调用它们的的layout()方法去设置它的坐标值。

draw过程

绘制View的过程。

从ViewRoot对象的performTraversals()调用draw()方法起,开始绘制View树。(只绘制“需要绘制的”,通过参考View的标志位:DRAWN)

1. View调用onDraw()方法绘制本身

2. ViewGroup调用dispatchDraw()方法绘制子视图

常见的方法

  1. invalidate()方法:

    请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程。

    除方法本身,如下方法会引起invalidate():setSelection()、setVisibility()、setEnabled()

  2. requestLayout()方法:

    对View树重新measure和layout的过程,不会调用draw过程,也不会重新绘制任何视图包括该调用者本身。

    在View的可视状态通过setVisibility()在由INVISIBLE/VISIBLE 转换为GONE时,会间接调用requestLayout() 和invalidate()方法。

  3. requestFocus()方法:

    会调用draw()过程重绘。

View绘制的工具类:Canvas、Paint、Bitmap

见之前写的这篇文章:

http://blog.csdn.net/a565815942/article/details/52002929

SurfaceView

使用SurfaceView的过程

  • 继承SurfaceView
  • 在初始化的时候拿到SurfaceHolder并给SurfaceHolder设置Callback
  • 实现surfaceCreated() surfaceChanged() surfaceDestroyed()方法
  • 在surfaceCreated中开启线程,在线程里通过surfaceHolder的lockCanvas 和 unlockCanvasAndPost方法锁定Canvas并绘图
  • 在surfaceDestroyed中结束这个绘图线程

绘图原理

Android的应用程序是通过SurfaceFlinger服务来绘制自己的UI的。

每个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描绘它的绘图表面。

而SurfaceView额外对应有一个Layer。SurfaceView在其宿主Activity窗口上设置了一块透明区域,以便它的UI可以对用户可见。

因此SurfaceView具体实现过程

  1. 创建绘图表面
  2. 宿主窗口设置一块透明
  3. 绘制

SurfaceView双缓存

SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储上一次更改前的视图.

当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。

View和SurfaceView比较

View:必须在UI的主线程中更新画面,用于被动更新画面。

surfaceView:UI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。

UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。

当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步,涉及到线程同步。