zoukankan      html  css  js  c++  java
  • invalidate和requestLayout方法源码分析

    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. ......
    9. //把我们的view追加到mContentParent
    10. mContentParent.addView(view, params);
    11. ......
    12. }
    我们继续看下这个方法中所调用的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.
  • 相关阅读:
    PHP中simpleXML递归实现XML文件与数组的相互转化(原创)
    关于本地服务器localhost请求Forbidden解决办法
    PHP中XPATH 实现xml及html文件快速解析(附xml做小型数据库实现六级单词快速查询实例)
    win8忘记开机密码解决方法汇总
    HTML5的FileAPI实现文件的读取及超大文件的上传
    FormData实现form表单的数据打包
    Ajax_iframe文件上传
    深入浅出JSONP--解决ajax跨域问题
    2017ACM暑期多校联合训练
    2017ACM暑期多校联合训练
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/2a3fccd829120089d24547929175ae29.html
Copyright © 2011-2022 走看看