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. ie8浏览器 图片本身问题导致 无法显示图片--- 诡异现象的排查分享

    引子:   前段时间 做新版2.0 首页 的时候, 总感觉 新版首页 线上 精彩回顾下的 2张图片颜色怪怪的,当时以为是图片压缩太厉害导致的,由于实在太忙就没太在意!以下 是来自线上 截图:  红色方 ...

  2. 如果修改GeneXus Android的一些源码文件(FlexibleClient)

    在使用GeneXus开发Android应用的过程中遇到了一个问题,使用tabs控件时发现默认高度过高,和UI设计要求的高度不一致,找了很久发现没有地方设置.后来联系了GeneXus中国厂商,得到了答复 ...

  3. 用于解析通过JS的escape函数加密过的数据

    function js_unescape($str) { $ret = ''; $len = strlen($str); for ($i = 0; $i < $len; $i++) { if ( ...

  4. RxSwift 系列(五)

    前言 本篇文章将要学习RxSwift中过滤和条件操作符,在RxSwift中包括了: filter distinctUntilChanged elementAt single take takeLast ...

  5. 【BZOJ 1923】1923: [Sdoi2010]外星千足虫 (高斯消元异或 | BITSET用法)

    1923: [Sdoi2010]外星千足虫 Description Input 第一行是两个正整数 N, M. 接下来 M行,按顺序给出 Charles 这M次使用“点足机”的统计结果.每行 包含一个 ...

  6. 山东省第四届ACM程序设计竞赛A题:Rescue The Princess

    Description Several days ago, a beast caught a beautiful princess and the princess was put in prison ...

  7. 主席树+dfs SPOJ BZOJ2588 Count on a tree

    这道题我由于智障错误导致一直错. 在树上建主席树,加上lca思想,很简单. #include<bits/stdc++.h> using namespace std; ; struct no ...

  8. POJ3710 Christmas Game 博弈论 sg函数 树的删边游戏

    http://poj.org/problem?id=3710 叶子节点的 SG 值为0:中间节点的SG值为它的所有子节点的SG值加1后的异或和. 偶环可以视作一个点,奇环视为一条边(连了两个点). 这 ...

  9. Luogu P3962 [TJOI2013]数字根 st

    题面 我先对数字根打了个表,然后得到了一个结论:\(a\)的数字根=\((a-1)mod 9+1\) 我在询问大佬后,大佬给出了一个简单的证明: \(\because 10^n\equiv 1(mod ...

  10. 【二分查找-最大化平均值】POJ2976 - Dropping Test

    [题目大意] 给出n组ai和bi,去掉k个使得a的总和除以b的总和最大. [思路] 也就是取(n-k)个数,最大化平均值,见<挑战程序设计竞赛>P144,最后公式为c(x)=((ai-x* ...