前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义View | invalidate()源码分析

自定义View | invalidate()源码分析

作者头像
凌川江雪
发布2020-10-26 16:26:20
6430
发布2020-10-26 16:26:20
举报
文章被收录于专栏:李蔚蓬的专栏

源码分析:知道原理为先,别一开始就往深处走, 要循序渐进

invalidate()源码

代码语言:javascript
复制
    public void invalidate() {
        invalidate(true);
    }

    /**
     * This is where the invalidate() work actually happens. A full invalidate()
     * causes the drawing cache to be invalidated, but this function can be
     * called with invalidateCache set to false to skip that invalidation step
     * for cases that do not need it (for example, a component that remains at
     * the same dimensions with the same content).
     *
     * @param invalidateCache Whether the drawing cache for this view should be
     *            invalidated as well. This is usually true for a full
     *            invalidate, but may be set to false if the View's contents or
     *            dimensions have not changed.
     * @hide
     */
    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }

        if (skipInvalidate()) {
            return;
        }

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            mPrivateFlags |= PFLAG_DIRTY;

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }

            // Damage the entire projection receiver, if necessary.
            if (mBackground != null && mBackground.isProjected()) {
                final View receiver = getProjectionReceiver();
                if (receiver != null) {
                    receiver.damageInParent();
                }
            }
        }
    }

invalidateInternal()

  • 首先调用了**invalidateInternal()**;
代码语言:javascript
复制
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) 
  • invalidateInternal()**中的**skipInvalidate()**肯定看得懂,** 即符合某几个条件 如界面不可见了(**(...) != VISIBLE**)、 或者没有设置动画**mCurrentAnimation == null**等 (**mCurrentAnimation**在View源码是一个全局变量,跟动画有关,可以看一下源码) 】, 则跳过**invalidate()**的调用 【直接**return**了,更不用说会调用**onDraw()**了】:
代码语言:javascript
复制
//invalidateInternal() 中的 skipInvalidate()

        if (skipInvalidate()) {
            return;
        }

...
//skipInvalidate() 源码

    /**
     * Do not invalidate views which are not visible and which are not running an animation. They
     * will not get drawn and they should not set dirty flags as if they will be drawn
     */
    private boolean skipInvalidate() {
        return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
                (!(mParent instanceof ViewGroup) ||
                        !((ViewGroup) mParent).isViewTransitioning(this));
    }
  • 往下继续看**invalidateInternal()**的代码,可以翻到: 这里调用了**ViewParent**的**invalidateChild()**:

ViewParent**自然是往**ViewGroup**的源码看,**

这里有个parent实例的赋值,直接赋值this,

接着主要看这里有个**do...while()**循环:

!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!

这个**do...while()**循环的目的就是不断地拿这个**parent**实例,

去进行一系列的操作;

其中每一次循环,

又拿这个**parent**实例去调用**invalidateChildInParent()**,

去获取这个**parent**实例的**parent**实例:

!!!!!!!!!!!!!!!!

如此不断循环,

最终会调用到**最外层**的**ViewGroup【View】**的**invalidateChildInParent()**;

!!!!!!!!!!!!!!!!

!!!!!!!!!!!!!!!!

代码语言:javascript
复制
            parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }

invalidateChildInParent()**源码:【会返回一个**ViewParent**实例或者空;】**

代码语言:javascript
复制
 @Deprecated
    @Override
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // either DRAWN, or DRAWING_CACHE_VALID
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
      ...
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }

                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
            } else {
...
            return mParent;
        }

        return null;
    }

接着这部分要在本地的SDK目录中找到这个**ViewRootImpl**源码文件,

然后把它拖到AS中阅读:

接着是:

其中最后调用了**scheduleTraversals();**

接着往下点,

接着往下点,

!!!!!!!!!!!!!!!!!!!!!

好了,重点就是这个**performTraversals()**方法,它在绘制流程中非常重要。

【一个近千行的方法】

!!!!!!!!!!!!!!!!!!!!!

其中又调用了三个重要的方法:

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

performLayout(lp, mWidth, mHeight);

performDraw()

!!!!!!!!!!!!!!!!!

setContentView()**的流程中,调用了以上三个方法,**

invalidate()**主要是调用了**performDraw()**方法,**

由于源码中存在条件判断,所以是不会进**performMeasure()**、**performLayout()**两个方法的;

!!!!!!!!!!!!!!!!!

所以下面主要看**performDraw()**方法,

performDraw()**中有一个draw方法,**

private boolean draw(boolean fullRedrawNeeded)

其中又调用了**drawSoftware()**,

drawSoftware()**中,出现了**Canvas**和**mSurface**,**

mSurface**锁定了Canvas【**lockCanvas()**】;大概了解一下就好;**

接着呢,又调用了**View的draw()**方法:

View的draw()**的源码**

我们在《自定义View | 基础概述 & 自定义TextView实战 & 基于源码分析自定义View继承自ViewGroup时无法正常绘制的问题》这篇笔记中有提及到,

下面是源码注释中总结的六个步骤:

小拓展——子线程中为什么不能更新UI? 源码角度稍微分析一下: 调用setText()、setImageView()等方法, 最后都会调到**ViewRootImpl**的**checkThread()**; checkThread()**是用来检测线程的;**

Thread.currentThread()**是子线程,**

mThread**是在构造函数中初始化的,**

这里的是主线程;

归结一下invalidate()的流程

简要浏览完源码了,归结一下**invalidate()**的大概流程:

首先从调用了**invalidate()**的**View**开始,

一路往上,跑到最外层的ViewRoot:

用最外层的View调用**draw()**,

draw()**如源码第四步有一个**dispatchDraw()**,又会一路往下画,**

不断的绘制**子孩子**,再绘制**子孩子**的**子孩子**,

最终绘制到 调用了**invalidate()**的**View**的 onDraw()**方法;**

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

所以当我们简简单单地调用一个View的**invalidate()**时,

它牵连的是**整个布局中**的**View实例**,

而不仅仅是 当前调用**invalidate()**的**View实例**一个 而已;

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • invalidate()源码
  • invalidateInternal()
  • 归结一下invalidate()的流程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档