《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 ...
随机推荐
- boost.asio包装类st_asio_wrapper开发教程(2014.5.23更新)(一)-----转
一:什么是st_asio_wrapper它是一个c/s网络编程框架,基于对boost.asio的包装(最低在boost-1.49.0上调试过),目的是快速的构建一个c/s系统: 二:st_asio_w ...
- Redisql: the lightning fast data polyglot【翻译】 - Linvo's blog - 博客频道 - CSDN.NET
Redisql: the lightning fast data polyglot[翻译] - Linvo's blog - 博客频道 - CSDN.NET Redisql: the lightnin ...
- IE_haslayout_与众多bug的纠缠
haslayout是什么: haslayout是IE的特有属性,就是has(有)layout(布局样式)! 在IE浏览器中,有的元素是默认“has” layout(有布局样式的),而有的元素是没有la ...
- html中的rowspan和colspan
摘自w3school(http://www.w3school.com.cn/tags/att_td_colspan.asp)colspan 属性规定单元格可横跨的列数.<table border ...
- (一)----使用HttpClient发送HTTP请求(通过get方法获取数据)
(一)----使用HttpClient发送HTTP请求(通过get方法获取数据) 一.HTTP协议初探: HTTP(Hypertext Transfer Protocol)中文 “超文本传输协议”,是 ...
- mina的编码和解码以及断包的处理,发送自己定义协议,仿qq聊天,发送xml或json
近期一段时间以来,mina非常火,和移动开发一样.异常的火爆.前面写了几篇移动开发的文章,都还不错.你们的鼓舞就是我最大的动力.好了,废话少说.我们来看下tcp通讯吧. tcp通讯对于java来说是非 ...
- Oracle的dbms_output包的put()和put_line()的区别只是有没有回车换行吗?(转)
答案是否 除了自动添加回车换行外,还有就是缓冲区最大容量的问题!! 无论如何设置serveroutput size,10g里 put() 最多只能输出 32767 个byte 而 put_line() ...
- http调试工具Charles Proxy用法详解
Charles Proxy 通常称为Charles,Charles是目前最强大的http调试工具,在界面和功能上远强于Fiddler,同时是全平台支持,堪称圣杯级工具,不过在这里为您提供了Charle ...
- 13年7月13日CF练习 Codeforces Round #147 (Div. 2)
这场div2可以说是我见过的比较水的一场吧.基本都是一眼题. 比赛地址http://acm.bnu.edu.cn/bnuoj/contest_show.php?cid=1836 题号是237A-237 ...
- 怎样用js得到当前页面的url信息方法(JS获取当前网址信息)
设置或获取对象指定的文件名称或路径.window.location.pathname 设置或获取整个 URL 为字符串.window.location.href; 设置或获取与 URL 关联的端口号码 ...