您的位置:首页 > 移动开发 > Android开发

Android View框架总结(二)View工作原理

2016-08-11 11:29 381 查看
测量/布局/绘制顺序
如何引起View的测量/布局/绘制?
PerformTraversales()
ViewRoot
View工作基本流程
MeasureSpec
SpecMode
MeasureSpec和LayoutParams
RootMeasureSpec

测量/布局/绘制顺序



View什么时候测量/布局/绘制?

Invalidate,requestLayout,requestFocus最终都会调用到ViewRoot中的schedulTraversale(),该函数发起一个异步消息,消息处理中调用performTraversals()方法对整个View进行遍历。

Invalidate

请求重绘view树,假如视图大小没有变化就不会调用layout(),只绘制那些需要重绘的视图,谁请求就重绘谁(ViewGroup调用就重绘整个ViewGroup)
requestLayout

只对view树重新layout,会导致调用measure和layout过程,不会调用draw()过程
requestFocus

请求view树的draw过程,但只绘制需要重绘的视图
setVisibility()

当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法, 当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图

ViewRoot

一个Window中View根节点DecorView, 它的mParent称为ViewRoot, 对应ViewRootImpl类, 它不是View的子类, 而是个ViewParent. ViewRootImpl是连接Window和DecorView的纽带, View的焦点, 按键, 布局, 渲染等流程都是从ViewRoot中开始的.

View绘制流程从requestLayout触发, View系统中所有会改变布局的方法都会触发requestLayout, 如TextView改变文字, ViewGroup添加View等.

View.java

<code class="hljs cs has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">requestLayout</span>() {
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;

<span class="hljs-keyword">if</span> (mParent != <span class="hljs-keyword">null</span> && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>

View的requestLayout最终调用到ViewRootImpl

ViewRootImpl.java

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">requestLayout</span>() {
checkThread();
mLayoutRequested = <span class="hljs-keyword">true</span>;
scheduleTraversals();
}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>

从scheduleTraversals名字来看, requestLayout只是触发一个异步的任务. 事实上, View真正的绘制流程是从ViewRootImpl的performTraversals方法开始, 里面会经过measure, layout和draw三个过程. 其中measure用来测量View的宽高, layout用来确定View的位置, 而draw负责渲染View到屏幕上. 大致流程如下:

performTraversals会依次调用performMeasure, performlayout和performDraw方法. 父容器measure方法会调用onMeasure, onMeasure方法会对所有子元素进行measure过程, 以此遍历完整个View树. layout和draw流程类似.

measure过程计算View的宽高, 先看measure方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec)

参数measureSpec是父容器传的对View的尺寸规格限制. 根节点DecorView的measureSpec在ViewRoot中计算出.

MeasureSpec

MeasureSpec字面上可以理解为尺寸规格, 测量过程中父容器会根据自己的MeasureSpec和子View的LayoutParams转换成对应的MeasureSpec, 子View根据这个MeasureSpec来计算出宽高, 因此我理解MeasureSpec是父容器对子元素的尺寸限制, 这样对下面的源码就好理解

View.java



使int 类型的高两位表示模式的实际值,其余30位代表长或宽的实际值—-可以是WRAP_CONTENT、MATCH_PARENT或具体大小exactly size。
通过掩码MODE_MASK进行与运算 “&”,取得模式(mode)以及长或宽(value)的实际值。
最高两位表示模式,后30位表示组件大小的值

最高两位是00的时候表示”未指定模式”。即MeasureSpec.UNSPECIFIED

最高两位是01的时候表示”’精确模式”。即MeasureSpec.EXACTLY

最高两位是11的时候表示”最大模式”。即MeasureSpec.AT_MOST

SpecMode

SpecMode有三种

UNSPECIFIED: 父容器不对View做任何限制. 一般在系统内部使用, 用于如ScrollView中, 或者需要多次测量来决定最终值的ViewGroup
EXACTLY: 父容器已经知道View精确大小是SpecSize, 或者限制View大小就是SpecSize
AT_MOST: 父容器指定View最大size是SpecSize, 一般在LayoutParams中是wrap_content或match_parent时使用.



MeasureSpec和LayoutParams

上面提到子View的MeasureSpec是根据LayoutParams和父容器的MeasureSpec转换来的, 虽然我们可以自己写转换算法, 但是系统里面已经提供了完善的算法. 除了DecorView的MeasureSpec是ViewRootImpl构造出来的, 其他View的转换方法都一样.

ViewGroup.java



上述方法就是对子元素进行measure的, 在measure之前通过getChildMeasureSpec方法得到子元素的MeasureSpec.

ViewGroup.java



上面的方法就是根据父容器的MeasureSpec结合View的LayoutParams转换子元素的MeasureSpec

方法中三个参数意义

spec: 父容器的MeasureSpec(这个未必是父容器的measure方法传入的MeasureSpec, 也可以根据情况构造一个)
padding: 父容器中已经被占用的空间, 如FrameLayout的padding值, LinearLayout前面View占据的空间等
childDimension: 子元素期望的size(或wrap_content/match_parent)

RootMeasureSpec

根节点View的MeasureSpec在performTraversals中得出. 先计算出窗口的最大可能尺寸desiredWindowWidth/desiredWindowHeight, 然后调用measureHierarchy方法来进入measure流程.:

ViewRootImpl.java



再看getRootMeasureSpec方法实现:

ViewRootImpl.java



DecorView的MeasureSpec根据窗口的LayoutParams按照如下规则生成

match_parent 明确大小就是窗口大小
wrap_content 最大不超过窗口大小
固定大小 明确大小就是LayoutParams指定的值
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: