Android绘制

I. 布局简单优化

  1. 尽量少的布局层级
  2. LinearLayout性能比RelativeLayout 稍高
  3. ViewStub代替include来引用不常用的布局
  4. merge来代替根节点是FrameLayout,并且不需要backgroundpadding等属性时。
  5. merge来代替include的顶节点,这样被引入时顶节点会自动被忽略。

II. 绘制相关深度优化

可以通过Hierarchy Viewer可视化布局,直观的看布局层级分布以及各View measurelayoutdraw 的耗时。
可以通过traceview,计算出每个方法所占用的CPU时间。

  1. 已知View大小的,自定义View,onMeasure时直接setMeasuredDimension
  2. 已知布局或者其他特定规律的,直接自定义View,达到减少层级,针对性measurelayoutdraw
  3. 如果布局含有复杂的动画,或者需要复杂的绘制,考虑在独立的绘制线程处理,而不block UI线程,此时考虑SurfaceViewTextureView(Android 4.0引入)(相比SurfaceView而言,可以像常规视图一样被改变)
  4. 使用OpenGL ES API进行绘制,可以更加针对性的高性能绘图。
  5. 如果资源图片比较大,考虑放在drawable-nodpi或者直接放在asset,防止获取资源的时候缩放暂用大量内存,也产生不必要的延时。

III. LayoutInflater

  • 使用XmlPull来解析
  • rInflate()方法(中不断递归)遍历根布局下的子布局
  • 由于setContentView默认是添加到id为contentFrameLayout中,因此LyoautParams有效。

最终结果:

是一个完整的DOM结构,返回的是顶层布局。

耗时点:

  1. 其中的createView()方法中通过反射创建出View实例

IV. 绘制过程

开始

ViewRootperformTraversals()

onMeasure()

measure()中调用,每个View都有一次measure()的过程.

参数: 规格和大小: MeasureSpec = specSize | specMode

规格说明:

名称注解
EXACTLY希望子视图大小 由 specSize决定
AT_MOST希望子视图大小 保证不超过 specSize
UNSPECIFIED希望子视图 任意大小(很少遇到)

widthMeasureSpecheightMeasureSpec参数由来:

  • 一般情况: 由父布局计算得到
  • 根布局: 由getRootMeasureSpec()处理得到:

根布局给的参数 | 规格 | 大小
-|-
MATCH_PARENT | EXACTLY | 视窗大小
WRAP_CONTENT | AT_MOST | 视窗大小
给定大小 | EXACTLY | 给定大小

ViewGroupmeasure:

遍历child View,进行measureChild

结合ViewGroup的规格与大小,以及child规格与大小获得参数传入child View进行子布局的measure

结束:

将最终结果通过setMeasuredDimension设置最终测量的结果,一次measure过程结束

注意: setMeasuredDimension()getMeasuredWidthgetMeasuredHeight才是有效值。

onLayout()

紧接着measure之后,就是布局,确定位置。调用Viewlayout()方法触发。

决定是否需要onLayout

  1. layout()中,首先会调用setFrame()方法来判断 视图是否发生过变化。
  2. 或者layout()中,有LAYOUT_REQUIRED(请求onLayout)

onDraw()

紧接着layout()之后,就是真正的绘制。调用Viewdraw()方法触发

步骤一,绘制背景

步骤二,为了淡入淡出做准备(一般没有)

如果有的情况下,一般情况下没有,就是保存canvas的Layers

步骤三,绘制内容

调用onDraw(Canvas),默认是空方法,这一部分是case by case

步骤四,绘制子View

调用dispatchDraw(Canvas),默认空方法,这一部分也是case by case

步骤五,绘制淡入淡出

如果有的情况下,绘制,然后还原canvas的Layer

步骤六,绘制滚动条

其实每个View都可以有滚动条的。

V. 视图状态

这里只提到需要特别注意到的。

View的视图状态变化,会回调View#drawableStateChange()

focused

  • requestFocus()不能保证一定能获取到焦点,返回值为true才表示获取成功。需要focusable && focusable in touch mode
  • 一个界面只有一个焦点

window_focused

  • 应用程序不能改变,由系统控制
  • 表示视图是否处于正在交互的窗口中

selected

  • 一个界面中可以有多个选中态

pressed

  • 实际上应用程序也可以通过setPressed()方法来控制的

VI. 状态变化回调

VII. View#invalidate

需要注意invalidate虽然最终调到performTraversals()但是很可能没有 重新测量标志,大小没有变化,因此不会执行measurelayout,只有draw可以执行到。
相比之下如果希望视图绘制流程完整重新走一遍,需要调用requestLayout


ps 第四篇是一些简单的应用层,就没有整理了




Android绘制
https://blog.dreamtobe.cn/2015/10/20/android-view/
作者
Jacksgong
发布于
2015年10月20日
许可协议