View状态分类

在View视图中定义了多种和界面效果相关的状态,比如拥有焦点Focused、按下Pressed等,不同的状态一般会显示不同的界面效果,而且视图状态会随着用户的操作而改变,一般通过xml文件中selector来申明各种状态下使用的背景图;所有的状态码位于StateListDrawable中,常用的状态码包括:

  1. enable:当前View是否可用,开发者可以通过setEnable()改变,他完全由开发者控制;
    当状态为不可用时,View将不会响应任何事件;
  2. focused:当前View是否正拥有焦点,一个窗口中只能有一个View拥有焦点,一般随用户操作而动态改变;
    该状态主要是针对按键的,因为所有的按键消息都将派发给focused视图;
  3. pressed:当前View是否正被按下,主要是针对触摸消息的,一般当用户按下时视图会有一个明显的变化,也是随用户操作而动态改变;
  4. 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标识,然后根据所有带该标识的视图边界一起确定最终要重绘的矩形区块,这里面会涉及到不同坐标体系间的换算,可以参见下图:

代码的具体执行过程是:

  1. View.invalidate()中设置必要的状态位标识之后,会执行到mParent.invalidateChild(..);
    这里的mParent有两种情况,一种是有父视图ViewGroup,另一种是已经到顶层了为ViewRootImpl;
  2. 若是ViewGroup,会执行完 invalidateChildInParent(…)之后继续调用mParent.invalidateChildInParent(…);
  3. 最终调用到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树遍历】相关推荐

  1. PMP读书笔记(第13章)

    大家好,我是烤鸭:     今天做一个PMP的读书笔记. 第十三章 项目相关方管理 项目相关方管理 项目相关方管理的核心概念 项目相关方管理的趋势和新兴实践 裁剪考虑因素 在敏捷或适应型环境中需要考虑 ...

  2. 《逆向工程核心原理》读书笔记——第13章 PE文件格式

    第13章 PE文件格式 13.1 介绍 13.2.PE文件格式 13.2.1基本结构 13.2.2 VA&RVA 13.3.2 DOS存根 13.3.3 NT头 13.3.4 NT头中的文件头 ...

  3. Linux内核分析 读书笔记 (第一章、第二章)

    第一章 Linux内核简介 1.1 Unix的历史 Unix很简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的. 在Unix中,所有东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相 ...

  4. Android进阶之光读书笔记——第三章:View体系与自定义View

    第三章 View体系与自定义View 本章将介绍Android中十分重要的View,在多本书中View是必讲的一节,Android群英传就讲了不少的View的知识,那么在这里我们再去复习一遍吧 3.1 ...

  5. UnixLinux大学教程 读书笔记【1-3章】

    Bash:Bourne-again shell,所有linux系统中的默认shell.程序名称:bash Shell:通过充当命令处理器以及解释命令的脚本,提供Unix基本界面的程序. 操作系统:运行 ...

  6. Android深度探索读书笔记 第七章

    第七章主要介绍led驱动.1测试led驱动:测试之前需要用USB数据线连接s3c开发板,然后打开S3C开发板的电源开关,成功启动后,执行build..sh脚本文件编译和安装led驱动.2 创建led驱 ...

  7. STL源码剖析读书笔记--第6章第7章--算法与仿函数

    老实说,这两章内容还蛮多的,但是其实在应用中一点点了解比较好.所以我决定这两张在以后使用过程中零零散散地总结,这个时候就说些基本概念好了.实际上,这两个STL组件都及其重要,我不详述一方面是自己偷懒, ...

  8. Android深度探索读书笔记 第六章

    Linux系统将每一个驱动都映射成一个文件.这些文件称为设备文件或驱动文件,都存在/dev目录中.编写linux驱动程序的步骤:1建立linux驱动骨架:linux内核在使用驱动时首先需要装载驱动.在 ...

  9. android工作机制和内核,android内核剖析学习笔记:AMS(ActivityManagerService)内部原理和工作机制...

    一.ActivityManagerService提供的主要功能: (1)统一调度各应用程序的Activity (2)内存管理 (3)进程管理 二.启动一个Activity的方式有以下几种: (1)在应 ...

  10. Javascript设计模式与开发实践读书笔记(1-3章)

    第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用   多态最根本好处在于,你不必询问对象"你是什么类型"而后根据得到的答案调用对象的某个行为--你只管调用 ...

最新文章

  1. iOS 加载本地和网络gif 图片类扩展
  2. catia 无许可证服务器名称,win10升级2004后CATIA无法连接许可证服务器解决方案(2页)-原创力文档...
  3. Eclipse 自动注释的设置
  4. idea黑色好还是白色好_白色牛仔裤,好看又好搭
  5. MySQL之EXPLAIN(索引优化)
  6. 《认清C++语言》之--内存管理
  7. mysql客户端备份数据库失败,mysqlhotcopy的使用和安装方法【快速备份mysql数据库】及错误解...
  8. 豆丁网文档免登陆免积分下载工具原理分析
  9. HTML-错误页面介绍
  10. ps怎么加底部阴影_ps影子(ps物体底部阴影怎么做)
  11. JAVA流的使用(复制文件效率对比)初学者
  12. 理科生学计算机数字媒体方向,文科生,理科生,分别可以报哪些专业?5分钟了解文理报考大方向...
  13. 油画特效软件akvis artwork破解版安装及破解教程
  14. 扁平化数组的几种方法
  15. UE4 如何开启 EQS
  16. 如何合理运用激励模式,实现有效激励
  17. 前端视角漫谈百度ueditor编辑器前后端分离配置
  18. Html(快捷键!+Tab)
  19. unsigned int a : 1;含义
  20. Otter 双向同步mysql

热门文章

  1. 如何修改硬盘挂载的名字LABEL
  2. B VUE系列 七:vue的脚手架项目是用webpack构建的本地服务环境,如何在手机上访问?...
  3. 一个Lumen多层拆分手记
  4. iOS 项目中的NSNotification简单使用
  5. linux一些基本知识
  6. Android输入法
  7. 商厂里调货拿包包程序
  8. 我的树莓派3配置脚本
  9. gtest测试代码编写思想
  10. Linux man 命令后面的圆括号的意义