《Android内核剖析》读书笔记 第13章 View工作原理【View树遍历】
View状态分类
在View视图中定义了多种和界面效果相关的状态,比如拥有焦点Focused、按下Pressed等,不同的状态一般会显示不同的界面效果,而且视图状态会随着用户的操作而改变,一般通过xml文件中selector来申明各种状态下使用的背景图;所有的状态码位于StateListDrawable中,常用的状态码包括:
- enable:当前View是否可用,开发者可以通过setEnable()改变,他完全由开发者控制;
当状态为不可用时,View将不会响应任何事件; - focused:当前View是否正拥有焦点,一个窗口中只能有一个View拥有焦点,一般随用户操作而动态改变;
该状态主要是针对按键的,因为所有的按键消息都将派发给focused视图; - pressed:当前View是否正被按下,主要是针对触摸消息的,一般当用户按下时视图会有一个明显的变化,也是随用户操作而动态改变;
- selected:当前View是否已被选中,一个窗口中可以有多个视图处于选中状态;开发者可以通过setSelected()改变,他完全由开发者控制;
导致View树重新遍历的总体诱因
遍历View树意味着整个View需要重新对其包含的子视图分配大小并重绘;一般情况下导致重新遍历的原因有三个:其一,视图本身内部状态发生变化,比如显示属性由GONE到VISIBLE;其二,ViewGroup中添加或删除了视图导致需要重新为子视图分配位置;其三,视图本身的大小发生变化,比如TextView中的文本内容变多变少了;
在代码层面这三种情况最后都会直接或间接调用到View中的三个函数:requestLayout/requestFocus/invalidate;由于是View树遍历,所以最后都会执行到最顶级父视图中的ViewRootImpl.scheduleTraversals();在该方法内,系统会发起一个异步消息(老版本中直接通过Handler发,新版本4.1中引入了Choreographer,以及对VSync和三级Buffer支持,让页面显示和操作更流畅,具体可以详见《Android Project Butter分析》),然后在异步消息执行过程中调用performTraversals()完成具体的View树遍历;可以参见下图:
View中超多的属性变量如何管理?
在庞大的View类中会涉及到非常多的状态码,比如是否可用、是否处于按下状态、是否需要重新分配位置、是否需要重绘等等;View树在遍历重绘时会根据不同的变量值来进行相应的操作,为此View中引入了bit标示位来管理这些状态值,分别用mViewFlags和mPrivateFlags变量来管理(随着状态码的增加,在新版本4.2中还有mPrivateFlags2/mPrivateFlags3变量),他们都是int类型的,也就是说理论上每个变量可以用来标示32个状态值,当对各个状态值修改时采用位运算符&|来完成;
其中mViewFlags变量主要用来保存和视图状态相关的值,比如是否可单击、是否可双击、是否可用、是否拥有焦点等;
mPrivateFlags变量主要用来保存和内部逻辑相关的属性,比如是否需要重新分配位置、是否需要重绘、是否刷新View缓存等;
注意:这两个变量之间是有紧密联系的,经常会需要两个变量同时设置某些状态值,可以参见setFlags(..)方法中的具体内容;
requestLayout()
该方法的执行过程很简单,因为当View树进行重新布局时,总是重新给所有的视图都进行布局,而不像重绘是可以指定只绘制某一个小区域的;
从代码层面他只是为mPrivateFlags变量添加FORCE_LAYOUT标识而已;然后逐层请求mParent.requestLayout();详见下图:
invalidate()
该方法的作用是请求View树重绘;视图及其父视图在界面上是分层先后显示的,父视图位于子视图下面,绘制过程中,首先绘制最底层的根视图,然后绘制其包含的子视图,子视图若是ViewGroup,则继续绘制其子视图,如此迭代至没有子视图为止;
在具体的重绘过程中,一般不会对所有视图都进行重绘,而是只绘制那些“需要绘制”的视图,那如何找出“需要绘制”的区域呢?这就是invalidate方法要完成的功能!
大致的思路是:当View需要重绘时会给mPrivateFlags变量添加DRAWN标识,然后根据所有带该标识的视图边界一起确定最终要重绘的矩形区块,这里面会涉及到不同坐标体系间的换算,可以参见下图:
代码的具体执行过程是:
- View.invalidate()中设置必要的状态位标识之后,会执行到mParent.invalidateChild(..);
这里的mParent有两种情况,一种是有父视图ViewGroup,另一种是已经到顶层了为ViewRootImpl; - 若是ViewGroup,会执行完 invalidateChildInParent(…)之后继续调用mParent.invalidateChildInParent(…);
- 最终调用到ViewRootImpl.invalidateChildInParent(…),进而执行scheduleTraversals();
注意:这里会提前判断mWillDrawSoon局部变量值,若当前已经在执行performTraversals()遍历重绘了,那就不会调用scheduleTraversals(),也就不会发起重绘的异步消息了,但View中设置的各种状态值仍然是有效的,只是会在下次重绘时生效;
scheduleTraversals()
该方法会在多个地方被调用,比如requestLayout()/invalidate()中,而我们又会经常会看到连续调用这两个方法的情况,那这样岂不是会发起两次View树遍历重绘请求?其实是不会的,因为在scheduleTraversals()方法内设置了一个局部变量mTraversalScheduled,若先执行了requestLayout(),那此时mTraversalScheduled为false,发起一个异步消息请求重绘,并将mTraversalScheduled变量值设为true,这样接着调用invalidate()时判断mTraversalScheduled变量值已经不是false了,这样就确保了只发起一个异步重绘请求;参见下图:
performTraversals()
该方法时系统内进行View树遍历并进行页面重绘的核心方法,内部逻辑还是非常复杂的,约800行代码;老实说偶目前还未完全看懂里面的细节,中间涉及的关联变量实在太多了;但大致的主体流程还是清晰的,就是根据之前设置好的各种状态值,判断是否需要重新计算视图大小(Measure)、是否需要重新分配视图的位置(也叫布局Layout)、以及是否需要重绘视图(Draw),框架过程参见下图,其中每项的具体过程详见后面的具体描述:
以上内容若有转载,请注明出处,欢迎访问老唐的专栏http://blog.csdn.net/sfdev
《Android内核剖析》读书笔记 第13章 View工作原理【View树遍历】的更多相关文章
- Android内核剖析读书笔记
第16章 程序包管理 PackageManagerService類 PmS 目錄 16.1 包管理概述 16.2 packages.xml文件格式 16.3 包管理服務的啟動過程 16.4 應用程序的 ...
- Android内核剖析读书笔记(1)—Framework概述
一.Framework组成 1.服务端组成 a.WindowManagerService 决定各窗口的叠放次序.隐藏或者显示窗口 b.ActivityManagerService 管理应用 ...
- APUE读书笔记-第13章-守护进程
第13章 守护进程 13.1 引言 *守护进程也称精灵进程(daemon)是生存期较长的一种进程.它们常常在系统自举时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的.UNI ...
- STL源码剖析读书笔记--第四章--序列式容器
1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...
- Linux内核分析 读书笔记 (第一章、第二章)
第一章 Linux内核简介 1.1 Unix的历史 Unix很简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的. 在Unix中,所有东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相 ...
- C++ primer plus读书笔记——第13章 类继承
第13章 类继承 1. 如果购买厂商的C库,除非厂商提供库函数的源代码,否则您将无法根据自己的需求,对函数进行扩展或修改.但如果是类库,只要其提供了类方法的头文件和编译后的代码,仍可以使用库中的类派生 ...
- JavaScript高级程序设计第三版-读书笔记(1-3章)
这是我第一次用markdown,也是我第一次在网上记录我自己的学习过程. 第一章 JavaScript主要由以下三个不同的部分构成 ECMAScript 提供核心语言功能 DOM 提供访问 ...
- 《Android内核剖析》读书笔记 第13章 View工作原理【View重绘过程】
计算视图大小的过程(Measure) 视图大小,准确的来说应该是指视图的布局大小:我们在layout.xml中为每个UI控件设置的layout_width/layout_height两个属性被用来设置 ...
- $《第一行代码:Android》读书笔记——第13章 Android高级技巧
(一)全局获取Context 1.创建ApplicationUtil类继承自Application类: public class ApplicationUtil extends Application ...
随机推荐
- Oracle实用-01:绑定变量
数据库虽然在学校系统学习过,但是在工作中真正使用起来收获又是不一样的,今天起打算将项目中使用到的技术再分享出来,不以书本的顺序,只从碰到的问题为顺序. 虽然不是纯粹的数据库工程师,但是每个程序员总免不 ...
- 2014Esri全球用户大会——亮点系列之精彩应用案例
在2014 Esri UC上,Esri邀请不少用户到场分享了自己企业的案例,在这里与大家进行分享. 一.City of Mineapolis 1.案例背景 Mineapolis市使用GIS已经数十年, ...
- hdu 4778 Rabbit Kingdom(减少国家)
题目链接:hdu 4778 Rabbit Kingdom 题目大意:Alice和Bob玩游戏,有一个炉子.能够将S个同样颜色的宝石换成一个魔法石.如今有B个包,每一个包里有若干个宝石,给出宝石的颜色. ...
- Swift - 异步获取网络数据封装类
使用NSURLConnection.sendAsynchronousRequest()可以采用异步获取的方式取得数据.下面通过对数据获取类进行封装,演示如何进行数据请求与接收. 1,HttpContr ...
- UVA 10795 - A Different Task(递归)
A Different Task The (Three peg) Tower of Hanoi problem is a popular one in computer science. Brie ...
- 排列-条件求和(Code)
static void Main(string[] args) { // Generate data int arraySize; int[] data; Random rnd; arraySize ...
- 忽然想到:QProcess的Read功能太强,如果有什么搞不定的,可以调用外部程序 good
这样就可以用其它语言来无限扩展它的功能了,比如golang,比如Delphi
- XML SelectSingleNode的使用 根据节点属性获取该节点
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Form ...
- haproxy 服务端超时时间 timeout server 17000 --后台程序17秒没有响应,返回超时
haproxy 服务端超时时间: haproxy 配置: timeout server 17000 --后台程序17秒没有响应,返回超时 Jun 27 09:29:56 localhost hapro ...
- VS2010使用静态编译的qt库
Qt开发界面很方便,但发布程序就不那么方便了,你的把引用到的dll一起发布才行,要是能静态编译就好了,发布的时候只有一个exe多方便. 虽然以前为了方便,直接安装的qt-windows-opensou ...