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

最新文章

  1. 推荐10款超级有趣的HTML5小游戏
  2. javascrit2.0完全参考手册(第二版) 第2章第3节 变量
  3. codevs 1835 魔法猪学院 A*寻k短路做了一个月卡死在spfa那了/(ㄒoㄒ)/~~
  4. 如何使weblogic11g类似weblogic923一样统一使用一个boot.properties文件
  5. Codeforces Round #306 (Div. 2) D. Regular Bridge 构造
  6. SNA社交网络算法
  7. Unity3d Awake、OnEnable、Start生命周期
  8. C#压缩与解压
  9. 怎样使用Android Studio开发Gradle插件
  10. SQL学习之查询
  11. Linux 管理软件
  12. App自动化(2)--Python&Appium实现安卓手机九宫格解锁
  13. 基于HA机制的Nginx配置实现
  14. heartFunction c语言
  15. dojo下的dom按钮与dijit/form/Button
  16. 9:集合collection
  17. "pip3 install requests"
  18. 跟着未名学Office - 高效工作Outlook
  19. .net中的SelectList在Html.DropdownList中的使用
  20. SDOI 2019 Round1 游记

热门文章

  1. 实现StatusBar的Flat风格
  2. Struts2通过自己定义拦截器实现登录之后跳转到原页面
  3. javascript (九)注释
  4. 在实体类中将数据库中数据类型为CLOB的数据转化成String类型
  5. CSS 控制应为Html页面高度导致抖动的问题
  6. uva 10196 Check The Check
  7. 理想非常丰满,现实非常骨感——致WiFi通话
  8. HDU--杭电--4506--小明系列故事——师兄帮帮忙--快速幂取模
  9. 第十七篇:实例分析(1)--初探WDDM驱动学习笔记(八)
  10. xcode6 cocos2dx开玩笑git和github学习记录