invalidate方法源码分析

在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewGroup没有重写这些方法),如下:

  1. /**
  2.  * Mark the area defined by dirty as needing to be drawn. dirty代表需要重新绘制的脏的区域
  3.  * If the view is visible, onDraw(Canvas) will be called at some point in the future.
  4.  * This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
  5.  * <b>WARNING:</b> In API 19 and below, this method may be destructive to dirty.
  6.  * @param dirty the rectangle矩形 representing表示 the bounds of the dirty region地区
  7.  */
  8. public void invalidate(Rect dirty) {
  9. final int scrollX = mScrollX;
  10. final int scrollY = mScrollY;
  11. invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
  12. dirty.right - scrollX, dirty.bottom - scrollY, true, false);
  13. }
  14. public void invalidate(int l, int t, int r, int b) {
  15. final int scrollX = mScrollX;
  16. final int scrollY = mScrollY;
  17. //实质还是调用invalidateInternal方法
  18. invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
  19. }
  20. /**
  21.  * Invalidate the whole view. 重新绘制整个View
  22.  */
  23. public void invalidate() {
  24. invalidate(true);
  25. }
  26. public void invalidate(boolean invalidateCache) {
  27. invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
  28. }
  29. //这是所有invalidate的终极调用方法
  30. void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
  31. ......
  32. // Propagate繁殖、传播 the damage损害的(只需要刷新的) rectangle to the parent view.
  33. final AttachInfo ai = mAttachInfo;
  34. final ViewParent p = mParent;
  35. if (p != null && ai != null && l < r && t < b) {
  36. final Rect damage = ai.mTmpInvalRect;
  37. damage.set(l, t, r, b);//将需要刷新的区域封装到damage中
  38. p.invalidateChild(this, damage);//调用Parent的invalidateChild方法,传递damage给Parent
  39. }
  40. ......
  41. }

由此可知,View的invalidate方法实质是将要刷新区域直接传递给了【父ViewGroup的invalidateChild方法】,这是一个从当前View向上级父View回溯的过程 。


我们看下ViewGroup的invalidateChild方法:
  1. public final void invalidateChild(View child, final Rect dirty) {
  2. ViewParent parent = this;
  3. ......
  4. do {
  5. ......
  6. //循环层层上级调用,直到ViewRootImpl会返回null
  7. parent = parent.invalidateChildInParent(location, dirty);
  8. ......
  9. } while (parent != null);
  10. }

这里面主要是一个循环,循环结束的条件是 parent == null,什么情况下parent为null呢?

这个问题之前有分析过,这里直接说结论了,就是循环当层层上级传递到ViewRootImpl时结束,我们看下ViewRootImpl的invalidateChildInParent的源码:
  1. @Override
  2. public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
  3. ......
  4. //View调用invalidate最终层层上传到ViewRootImpl后最终触发了该方法
  5. scheduleTraversals();
  6. ......
  7. return null;
  8. }

也就是上面说的,当层层上级传递到ViewRootImpl的invalidateChildInParent方法时,返回了null,结束了那个do while循环。


这里调用的scheduleTraversals会通过Handler发送一个异步消息,收到消息后会回调doTraversal方法,最终调用performTraversals执行重绘。

performTraversals方法就是整个View树开始绘制的起始节点,所以,View调用invalidate方法的实质是:层层上传到父级,直到传递到ViewRootImpl后会触发scheduleTraversals方法,然后整个View树就开始重新按照View的绘制流程进行重绘任务。

这里再按正序说下整个View树的绘图流程:
整个View树的绘图流程是在【ViewRootImpl】类的【performTraversals】方法开始的,该函数做的执行过程主要是根据之前设置的【状态】,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重新绘制(draw),其核心也就是通过判断来选择按顺序执行这三个方法中的哪几个。

到此View的invalidate方法原理就分析完成了。

invalidate方法执行的过程图:

postInvalidate方法源码分析

上面分析invalidate方法时注释中说该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate方法,所以我们来分析分析postInvalidate这个方法源码。如下:
  1. public void postInvalidate() {
  2. postInvalidateDelayed(0);
  3. }
  4. public void postInvalidateDelayed(long delayMilliseconds) {
  5. // We try only with the AttachInfo because there's no point in invalidating
  6. // if we are not attached to our window
  7. final AttachInfo attachInfo = mAttachInfo;
  8. //核心,实质就是调用了ViewRootImpl.dispatchInvalidateDelayed方法
  9. if (attachInfo != null) {
  10. attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
  11. }
  12. }
  13. public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
  14. Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
  15. mHandler.sendMessageDelayed(msg, delayMilliseconds);
  16. }

看见没有,通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,继续追踪这条消息的处理可以发现:

  1. public void handleMessage(Message msg) {
  2.     ......
  3.     switch (msg.what) {
  4.     case MSG_INVALIDATE:
  5.         ((View) msg.obj).invalidate();
  6.         break;
  7.     ......
  8.     }
  9.     ......
  10. }

看见没有,实质就是又在UI Thread中调用了View的invalidate()方法,所以可以说,除了调用所在线程不一样之外,其他的和invalidate()方法都是一样的。


invalidate与postInvalidate方法总结

invalidate系列方法请求重绘View树时,如果View【大小】没有发生变化就不会调用layout过程,并且只绘制那些"需要重绘的"View,也就是哪个View请求invalidate系列方法,就绘制该View。

常见的引起invalidate方法操作的原因主要有:
  • 直接调用invalidate方法,请求重新draw,但只会绘制调用者本身。
  • 触发setSelection方法,请求重新draw,但只会绘制调用者本身。
  • 触发setEnabled方法,请求重新draw,但不会重新绘制任何View包括该调用者本身。
  • 触发requestFocus方法,请求View树的draw过程,只绘制"需要重绘"的View。
  • 触发setVisibility方法, 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要"重新绘制"的视图。

补充performTraversals方法调用时机

之前我们说过:整个View绘制流程的最初代码是在ViewRootImpl类的performTraversals()方法中开始的,上面当时只是告诉你了这个结论,至于这个ViewRootImpl类的performTraversals()方法为何会被触发没有说明原因,现在我们就来分析一下这个触发的源头。

我们先来看下之前博文中分析过的,Activity中setContentView方法所调用的PhoneWindow中的setContentView方法的源码:
  1. @Override
  2. public void setContentView(View view, ViewGroup.LayoutParams params) {
  3. ......
  4. //如果mContentParent为空进行一些初始化
  5. if (mContentParent == null) {
  6. installDecor();
  7. ......
  8. //把我们的view追加到mContentParent
  9. mContentParent.addView(view, params);
  10. ......
  11. }

我们继续看下这个方法中所调用的addView方法,也就是ViewGroup的addView方法,如下:

  1. public void addView(View child) {
  2. addView(child, -1);
  3. }
  4. public void addView(View child, int index) {
  5. ......
  6. addView(child, index, params);
  7. }
  8. public void addView(View child, int index, LayoutParams params) {
  9. ......
  10. //该方法稍后后面会详细分析
  11. requestLayout();
  12. //重点关注!!!
  13. invalidate(true);
  14. ......
  15. }

看见addView调用invalidate方法没有?这不就真相大白了。当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会将我们的View通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调用invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。


requestLayout方法源码分析

和invalidate类似,之前在分析View绘制流程时或多或少都调用到了这个方法,而且这个方法对于View来说也比较重要,所以我们接下来分析一下他。如下为View的requestLayout源码:
  1. public void requestLayout() {
  2. ......
  3. if (mParent != null && !mParent.isLayoutRequested()) {
  4. //从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout
  5. mParent.requestLayout();
  6. }
  7. ......
  8. }

看见没有,整个和invalidate类似,当我们触发View的requestLayout时其实质就是:层层向上传递,直到ViewRootImpl为止,最后触发ViewRootImpl的requestLayout方法。

如下就是ViewRootImpl的requestLayout方法:
  1. @Override
  2. public void requestLayout() {
  3. if (!mHandlingLayoutInLayoutRequest) {
  4. checkThread();
  5. mLayoutRequested = true;
  6. //View调用requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
  7. scheduleTraversals();
  8. }
  9. }

看见没有,依旧是和invalidate类似,并且最终调用的也是scheduleTraversals()这个方法,只是在上传过程中所设置的标记不同,最终导致对于View的绘制流程中所触发的方法不同而已。


requestLayout方法总结

对于requestLayout方法来说,总结如下:
  • requestLayout()方法会调用【measure】过程和【layout】过程,不会调用【draw】过程,所以不会重新绘制任何View(包括该调用者本身)。
  • 使用条件:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求父view重新调用他的onMeasure、onLayout来重新设置自己位置。
  • 用途:有时我们在改变一个view 的内容之后,可能会造成显示出现错误,比如写ListView的时候 重用convertview中的某个TextView 可能因为前后填入的text长度不同而造成显示出错,此时我们可以在改变内容之后调用requestLayout方法加以解决。

Google文档的英文说明:
Call this when something has changed which has invalidated the layout of this view 当View的布局已经无效时调用. This will schedule重新安排 a layout pass of通过 the view tree. This should not be called while the view hierarchy层次 is currently in a layout pass (isInLayout(). If layout is happening, the request may be honored at the end of the current layout pass (and then layout will run again) or after the current frame is drawn and the next layout occurs. 
Subclasses子类 which override this method should call the superclass method to handle possible request-during-layout errors correctly.

invalidate和requestLayout方法源码分析的更多相关文章

  1. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

  2. Linq分组操作之GroupBy,GroupJoin扩展方法源码分析

    Linq分组操作之GroupBy,GroupJoin扩展方法源码分析 一. GroupBy 解释: 根据指定的键选择器函数对序列中的元素进行分组,并且从每个组及其键中创建结果值. 查询表达式: var ...

  3. 【Java】NIO中Selector的select方法源码分析

    该篇博客的有些内容和在之前介绍过了,在这里再次涉及到的就不详细说了,如果有不理解请看[Java]NIO中Channel的注册源码分析, [Java]NIO中Selector的创建源码分析 Select ...

  4. jQuery实现DOM加载方法源码分析

    传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但 ...

  5. jQuery.extend()方法和jQuery.fn.extend()方法源码分析

    这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> ...

  6. jQuery.clean()方法源码分析(一)

    在jQuery 1.7.1中调用jQuery.clean()方法的地方有三处,第一次就是在我之前的随笔分析jQuery.buildFramgment()方法里面的,其实还是构造函数的一部分,在处理诸如 ...

  7. HashMap put、get方法源码分析

    HashMap.java的实现是面试必问的问题. JDK版本 java version "1.8.0_91" Java(TM) SE Runtime Environment (bu ...

  8. HashMap主要方法源码分析(JDK1.8)

    本篇从HashMap的put.get.remove方法入手,分析源码流程 (不涉及红黑树的具体算法) jkd1.8中HashMap的结构为数组.链表.红黑树的形式     (未转化红黑树时)   (转 ...

  9. Hystrix微服务容错处理及回调方法源码分析

    前言 在 SpringCloud 微服务项目中,我们有了 Eureka 做服务的注册中心,进行服务的注册于发现和服务治理.使得我们可以摒弃硬编码式的 ip:端口 + 映射路径 来发送请求.我们有了 F ...

随机推荐

  1. Python全栈开发之5、几种常见的排序算法以及collections模块提供的数据结构

    转载请注明出处http://www.cnblogs.com/Wxtrkbc/p/5492298.html 在面试中,经常会遇到一些考排序算法的题,在这里,我就简单了列举了几种最常见的排序算法供大家学习 ...

  2. Error:Unable to make the module:***, related gradle configuration was not found. Please, re-import the Gradle project and try again.

    打开idea的 View -> Tool Windows -> Gradle.然后点击 Refresh

  3. ubuntu各种软件安装-装机整套系列

    首先声明,本人系统ubuntu 14.04.1 LTS, 以下所有软件均安装于该系统. 一. 首先在windows下删除ubuntu,删除方法如下: 1.进入win7,下载个软件MbrFix,放在C: ...

  4. 打开tcp_tw_recycle引起的一个问题

    今天普空说了一个问题就是如果设置了tcp_tw_recycle ,那么如果客户端是NAT出来的,那么就可能会出现连接被直接rst的情况.然后我google了下,在内核列表也有人说了这个问题 https ...

  5. nyoj 269 VF 动规

    VF 时间限制:1000 ms  |  内存限制:65535 KB 难度:2 描述 Vasya is the beginning mathematician. He decided to make a ...

  6. BZOJ 4036: [HAOI2015]按位或 集合幂函数 莫比乌斯变换 莫比乌斯反演

    http://www.lydsy.com/JudgeOnline/problem.php?id=4036 http://blog.csdn.net/lych_cys/article/details/5 ...

  7. SpringBoot 如何从前台传递数组

    1.SpringBoot 如何从前台传递数组 2.前台 $.ajax({ url: 'deleteBsGiftById', type: 'post', dataType: 'json', data: ...

  8. [bzoj1022][SHOI2008]小约翰的游戏 John (博弈论)

    Description 小约翰经常和他的哥哥玩一个非常有趣的游戏:桌子上有n堆石子,小约翰和他的哥哥轮流取石子,每个人取的时候,可以随意选择一堆石子,在这堆石子中取走任意多的石子,但不能一粒石子也不取 ...

  9. python开发_linecache

    #从linecache的名称,我们可以知道该模块和cache(缓存)有关 #linecache现把文件读入到缓存中,在以后访问文件的时候,就不必要再从硬盘读取 #所以经常用于那些读取频率很高的文件还可 ...

  10. FPGA LVDS I/O as an Analog Programmable Comparator

    http://www.eetimes.com/author.asp?section_id=36&doc_id=1320289 Seeing the new ADC IP being bandi ...