美文网首页
invalidate和requestLayout之源码区别

invalidate和requestLayout之源码区别

作者: 瑜小贤 | 来源:发表于2020-03-24 19:22 被阅读0次

    1. invalidate

    /**
         * Indicates that this view was specifically invalidated, not just dirtied because some
         * child view was invalidated. The flag is used to determine when we need to recreate
         * a view's display list (as opposed to just returning a reference to its existing
         * display list).
         *
         * @hide
         */
        static final int PFLAG_INVALIDATED                 = 0x80000000;
    
    
        static final int PFLAG_DRAWING_CACHE_VALID         = 0x00008000;
    

    看起来好像,子view invalidate就会导致父view PFLAG_INVALIDATED
    但实际上,子view invalidate只会导致自身 PFLAG_INVALIDATED,父view的PFLAG_INVALIDATED不会被设置。
    PFLAG_DRAWING_CACHE_VALID代表drawing_cache有效了,这个才是其中某个子view invalidate了,一个子view invalidate会导致父view的PFLAG_DRAWING_CACHE_VALID被置0,父view的父view也会PFLAG_DRAWING_CACHE_VALID被置为0,后文会分析

    1.1 View#invalidateInternal

    public void invalidate() {
        invalidate(true);
    }
    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;
            }
    
            //这里判断该子View是否可见或者是否处于动画中
            if (skipInvalidate()) {
                return;
            }
    
            // Reset content capture caches
            mCachedContentCaptureSession = null;
    
            //根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
            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;
                }
    
                //设置PFLAG_DIRTY标记位
                mPrivateFlags |= PFLAG_DIRTY;
    
                //invalidateCache一般为true
                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();
                    }
                }
            }
    }
    

    invalidate有多个重载方法,但最终都会调用invalidateInternal方法,在这个方法内部,进行了一系列的判断,判断View是否需要重绘,接着为该View设置标记位,然后把需要重绘的区域传递给父容器,即调用父容器的invalidateChild方法。
    注意其中:

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

    2个标志位PFLAG_INVALIDATEDPFLAG_DRAWING_CACHE_VALIDPFLAG_INVALIDATED置为1,PFLAG_DRAWING_CACHE_VALID置为0。

    1.2 ViewGroup#invalidateChild

    public final void invalidateChild(View child, final Rect dirty) {
            ...
            //设置 parent 等于自身
            ViewParent parent = this;
            if (attachInfo != null) {
                final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
    
                Matrix childMatrix = child.getMatrix();
    
                if (child.mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                    mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
                }
    
                //储存子View的mLeft和mTop值
                final int[] location = attachInfo.mInvalidateChildLocation;
                location[CHILD_LEFT_INDEX] = child.mLeft;
                location[CHILD_TOP_INDEX] = child.mTop;
                ...
    
                do {
                    View view = null;
                    if (parent instanceof View) {
                        view = (View) parent;
                    }
    
                    if (drawAnimation) {
                        if (view != null) {
                            view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                        } else if (parent instanceof ViewRootImpl) {
                            ((ViewRootImpl) parent).mIsAnimating = true;
                        }
                    }
    
                    // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                    // flag coming from the child that initiated the invalidate
                    if (view != null) {
                        if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                            //对当前View的标记位进行设置
                            view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
                        }
                    }
                    
                    //调用ViewGroup的invalidateChildInParent
                    //如果已经达到最顶层view,则调用ViewRootImpl的invalidateChildInParent。
                    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);
            }
        }
    

    可以看到,在该方法内部,先设置当前视图的标记位,接着有一个do-while循环,不停调用父viewinvalidateChildInParent,一直到调用ViewRootImplinvalidateChildInParent,作用就是不断向上回溯父容器,求得父容器和子View需要重绘的区域的并集(dirty),当父容器不是ViewRootImpl的时候,调用的是ViewGroup的invalidateChildInParent方法。
    注意其中:

    if (child.mLayerType != LAYER_TYPE_NONE) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }
    

    PFLAG_DRAWING_CACHE_VALID置为0,在do-while循环后,当前view的所有父view,父view的父view。。。都会被PFLAG_DRAWING_CACHE_VALID置为0。

    1.3 ViewGroup # invalidateChildInParent

    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))
                        != FLAG_OPTIMIZE_INVALIDATE) {
                    //将dirty中的坐标转化为父容器中的坐标,考虑mScrollX和mScrollY的影响
                    dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                            location[CHILD_TOP_INDEX] - mScrollY);
    
                    if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                        //求并集,结果是把子视图的dirty区域转化为父容器的dirty区域
                        dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                    }
    
                    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();
                        }
                    }
    
                    //记录当前视图的mLeft和mTop值,在下一次循环中会把当前值再向父容器的坐标转化
                    location[CHILD_LEFT_INDEX] = left;
                    location[CHILD_TOP_INDEX] = top;
                } else {
    
                    if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                        dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                    } else {
                        // in case the dirty rect extends outside the bounds of this container
                        dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                    }
                    location[CHILD_LEFT_INDEX] = mLeft;
                    location[CHILD_TOP_INDEX] = mTop;
    
                    mPrivateFlags &= ~PFLAG_DRAWN;
                }
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }
    
                //返回当前视图的父容器
                return mParent;
            }
    
            return null;
        }
    

    可以看出,调用invalidateChildInParent会传进去一个Rectdirty,代表子窗口需要刷新的Rect,调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标,然后父窗口会根据这个Rect和父窗口本身做并集union,从而得到父窗口需要刷新的Rect区域,然后再传给父窗口的父窗口,一直递归直到ViewRootImpl
    换句话说,dirty区域变成父容器区域。最后返回当前视图的父容器,以便进行下一次循环。

    1.4 ViewRootImpl # invalidateChildInParent

    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
    
            if (dirty == null) {
                invalidate();
                return null;
            } else if (dirty.isEmpty() && !mIsAnimating) {
                return null;
            }
    
            if (mCurScrollY != 0 || mTranslator != null) {
                mTempRect.set(dirty);
                dirty = mTempRect;
                if (mCurScrollY != 0) {
                    dirty.offset(0, -mCurScrollY);
                }
                if (mTranslator != null) {
                    mTranslator.translateRectInAppWindowToScreen(dirty);
                }
                if (mAttachInfo.mScalingRequired) {
                    dirty.inset(-1, -1);
                }
            }
            //这个方法会去调用scheduleTraversals方法
            invalidateRectOnScreen(dirty);
    
            return null;
        }
    
    private void invalidateRectOnScreen(Rect dirty) {
            final Rect localDirty = mDirty;
    
            // Add the new dirty rect to the current one
            localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
            // Intersect with the bounds of the window to skip
            // updates that lie outside of the visible region
            final float appScale = mAttachInfo.mApplicationScale;
            final boolean intersected = localDirty.intersect(0, 0,
                    (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
            if (!intersected) {
                localDirty.setEmpty();
            }
            if (!mWillDrawSoon && (intersected || mIsAnimating)) {
                scheduleTraversals();
            }
        }
    
    void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    

    可以看出,该方法所做的工作与上面的差不多,都进行了offsetunion对坐标的调整,然后把dirty区域的信息保存在mDirty中,最后调用了invalidateRectOnScreen方法,这个方法会去调用scheduleTraversals方法,触发View的工作流程,之前梳理过View的绘制流程,performTraversals方法中是有performMeasureperformLayout()performDraw()三个绘制方法的。但是,这里由于没有添加measurelayout的标记位,因此measurelayout流程不会执行,而是直接从draw流程开始。
    稍微捋一下scheduleTraversals后的方法,

    1. mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 执行 mTraversalRunnable这个Runnable
    2. 执行run方法里的执行doTraversal方法
    3. 执行performTraversals
    4. 执行performDraw
    5. 执行draw
    6. 执行mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this),mThreadedRenderer是一个ThreadedRenderer
    7. ThreadedRenderer类中的draw方法继续调用updateRootDisplayList方法
    8. 执行updateViewTreeDisplayList方法
    9. 执行updateDisplayListIfDirty方法
    10. if 是父族view 或者是 invalidate的view本身,进去调用dispatchGetDisplayList方法,反之进else,设设标识就完事了
    private void updateViewTreeDisplayList(View view) {
            view.mPrivateFlags |= View.PFLAG_DRAWN;
            view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                    == View.PFLAG_INVALIDATED;
            view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
            view.updateDisplayListIfDirty();
            view.mRecreateDisplayList = false;
        }
    

    此时viewDecorViewDecorView会往下分发找到需要重绘的view,然后调用此view的draw方法。假设调用invalidate的view为a,a的parent为ap。我们知道此时a的PFLAG_DRAWING_CACHE_VALID 为0,PFLAG_INVALIDATED为1。而a的父族view,ap,app,appp等的PFLAG_DRAWING_CACHE_VALID为0,PFLAG_INVALIDATED为0.
    所以上边会把mRecreateDisplayList设置为false,后面会知道mRecreateDisplayList代表了哪个view要被重绘的标记(在ViewupdateDisplayListIfDirty方法中就会把它置为true),只有invalidate的view a的mRecreateDisplayListtrue,其他都是false

    再来看DecorView如何分发的。DecorViewupdateViewTreeDisplayList会调updateDisplayListIfDirty,如下,因为DecorView是a的父族view,所以会进@AAA的if,然后mRecreateDisplayListfalse,所以会进@BBB调用 dispatchGetDisplayList,而invalidate的view a却进不去@BBB,因为它的mRecreateDisplayList是true,所以它会走到下面的try方法体里,进行重绘。

    View # updateDisplayListIfDirty
    public RenderNode updateDisplayListIfDirty() {
            final RenderNode renderNode = mRenderNode;
            if (!canHaveDisplayList()) {
                // can't populate RenderNode, don't try
                return renderNode;
            }
    
            //@AAA
            if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                    || !renderNode.hasDisplayList()
                    || (mRecreateDisplayList)) {
                // Don't need to recreate the display list, just need to tell our
                // children to restore/recreate theirs
               //@BBB
                if (renderNode.hasDisplayList()
                        && !mRecreateDisplayList) {
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchGetDisplayList();
    
                    return renderNode; // no work needed
                }
    
                // If we got here, we're recreating it. Mark it as such to ensure that
                // we copy in child display lists into ours in drawChild()
                mRecreateDisplayList = true;
    
                int width = mRight - mLeft;
                int height = mBottom - mTop;
                int layerType = getLayerType();
    
                final RecordingCanvas canvas = renderNode.beginRecording(width, height);
    
                try {
                    if (layerType == LAYER_TYPE_SOFTWARE) {
                        buildDrawingCache(true);
                        Bitmap cache = getDrawingCache(true);
                        if (cache != null) {
                            canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                        }
                    } else {
                        computeScroll();
    
                        canvas.translate(-mScrollX, -mScrollY);
                        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    
                        // Fast path for layouts with no backgrounds
                        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                            //@CCC
                            dispatchDraw(canvas);
                            drawAutofilledHighlight(canvas);
                            if (mOverlay != null && !mOverlay.isEmpty()) {
                                mOverlay.getOverlayView().draw(canvas);
                            }
                            if (debugDraw()) {
                                debugDrawFocus(canvas);
                            }
                        } else {
                            draw(canvas);
                        }
                    }
                } finally {
                    renderNode.endRecording();
                    setDisplayListProperties(renderNode);
                }
            } else {
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            }
            return renderNode;
        }
    
    
    ViewGroup # dispatchGetDisplayList
    protected void dispatchGetDisplayList() {
            final int count = mChildrenCount;
            final View[] children = mChildren;
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                    recreateChildDisplayList(child);
                }
            }
            if (mOverlay != null) {
                View overlayView = mOverlay.getOverlayView();
                recreateChildDisplayList(overlayView);
            }
            if (mDisappearingChildren != null) {
                final ArrayList<View> disappearingChildren = mDisappearingChildren;
                final int disappearingCount = disappearingChildren.size();
                for (int i = 0; i < disappearingCount; ++i) {
                    final View child = disappearingChildren.get(i);
                    recreateChildDisplayList(child);
                }
            }
        }
    
    private void recreateChildDisplayList(View child) {
            child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
            child.mPrivateFlags &= ~PFLAG_INVALIDATED;
            child.updateDisplayListIfDirty();
            child.mRecreateDisplayList = false;
        }
    

    dispatchGetDisplayList的作用主要就是一个for循环,内调用recreateChildDisplayList,让子view recreate & display

    总结

    parent: updateDisplayListIfDirty -> dispatchGetDisplayList -> for() recreateChildDisplayList
    child: recreateChildDisplayList -> updateDisplayListIfDirty
    我们在具体分析下各个view会如何表现,核心代码是updateDisplayListIfDirty,

    父族view的执行流程是
    parent: updateDisplayListIfDirty -> dispatchGetDisplayList -> for() recreateChildDisplayList
    child: recreateChildDisplayList -> updateDisplayListIfDirty

    如果是a本身,那么@AAA进去,但是由于a的mRecreateDisplayList为true,所以进不去@BBB会到下面的try方法块进行重绘,这就是invalidate导致的分发过程

    如果不是父族view也不是a本身,那么PFLAG_DRAWING_CACHE_VALID为1,会走@AAA的else设置标志位,然后结束,不会分发到子view。
    所以整个过程中只有a调用了draw方法进行重绘,这是PFLAG_INVALIDATED标志位起的作用

    ViewGroup的invalidate

    我们再来看看ViewGroup的invalidate,刚才说了invalidate如果是个view,那就只有自己本身会draw,如果是ViewGroup呢?
    因为一般的ViewGroup都是SKIP_DRAW的,所以会走到@CCCdispatchDraw,dispatchDraw的实现一般在ViewGroup里,就是**调用子view的draw(注意是调用的3参的draw方法,而不是单参的方法)** 所以一般来说ViewGroupinvalidate`就是对子view进行重绘。(android.view.View#draw(android.graphics.Canvas, android.view.ViewGroup, long))

    用人话说:当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。

    1. view的invalidate并不会调用ViewRootImplinvalidate
    2. performDraw的过程中,大部分view的updateDisplayListIfDirty都会被调用,但是只有设了标志位的view会调用draw方法进而调用onDraw

    PostInvalidate

    这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

    public void postInvalidate() {
        postInvalidateDelayed(0);
    }
    
    public void postInvalidateDelayed(long delayMilliseconds) {
        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
        }
    }
    

    由以上代码可以看出,只有attachInfo不为null的时候才会继续执行,即只有确保视图被添加到窗口的时候才会通知view树重绘,因为这是一个异步方法,如果在视图还未被添加到窗口就通知重绘的话会出现错误,所以这样要做一下判断。接着调用了ViewRootImpl#dispatchInvalidateDelayed方法:

    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }
    

    这里用了Handler,发送了一个异步消息到主线程,显然这里发送的是MSG_INVALIDATE,即通知主线程刷新视图,具体的实现逻辑我们可以看看该mHandler的实现:

    final ViewRootHandler mHandler = new ViewRootHandler();
    
    final class ViewRootHandler extends Handler {
            @Override
            public String getMessageName(Message message) {
                ....
            }
    
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case MSG_INVALIDATE:
                    ((View) msg.obj).invalidate();
                    break;
                ...
            }
        }
    }
    

    可以看出,参数message传递过来的正是View视图的实例,然后直接调用了invalidate方法,然后继续invalidate流程。

    requestLayout

    从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程。但是,真实情况略有不同,如果子View调用了这个方法,其实会从View树重新进行一次测量、布局、绘制这三个流程,最终就会显示子View的最终情况。那么,这个方法是怎么实现的呢?我们从源码角度进行解析。

    View # requestLayout
    //从源码注释可以看出,如果当前View在请求布局的时候,View树正在进行布局流程的话,
    //该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行。
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
    
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
    
        //为当前view设置标记位 PFLAG_FORCE_LAYOUT
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
    
        if (mParent != null && !mParent.isLayoutRequested()) {
            //向父容器请求布局
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
    

    requestLayout方法中,首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位,该标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用mParent.requestLayout方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,即根View,而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl能够处理requestLayout事件

    ViewRootImpl # requestLayout:
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    

    我们可以简单的认为mLayoutRequestedtrue会触发performMeasure(内部会调用onMeasure)和performLayout(内部会调用onLayout)。然后在performDraw内部draw的过程中发现mDirty为空,所以onDraw不会被调用,不重绘。
    这么看来requestLayout不会导致onDraw调用了?
    也不见得,我们知道requestLayout会导致performMeasureperformLayout,如果在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate。代码在ViewsetFrame中,这个会在layout时被调用。

    protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
    
            if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
                changed = true;
    
                // Remember our drawn bit
                int drawn = mPrivateFlags & PFLAG_DRAWN;
    
                int oldWidth = mRight - mLeft;
                int oldHeight = mBottom - mTop;
                int newWidth = right - left;
                int newHeight = bottom - top;
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
    
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
                mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
                。。。
        }        
    

    所以requestLayout有可能会导致onDraw被调用,也可能不导致onDraw被调用,取决于view的l,t,r,b是否改变。

    一些知识点:

    1. view不停找parent可以一直找到DecorView,按理说DecorView是顶点了,但是DecorView还有个虚拟父view:ViewRootImplViewRootImpl不是一个View或者ViewGroup,他有个成员mView是DecorView,所有的操作从ViewRootImpl开始自上而下分发
    2. view的invalidate不会导致ViewRootImplinvalidate被调用,而是递归调用父view的invalidateChildInParent,直到ViewRootImplinvalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,由于mLayoutRequestedfalse,不会导致onMeasureonLayout被调用,而OnDraw会被调用
    3. 一个view的invalidate会导致本身PFLAG_INVALIDATED1,导致本身以及父族viewgroup的PFLAG_DRAWING_CACHE_VALID0
    4. requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequestedtrue,会导致onMeasureonLayout被调用。不一定会触发onDraw
    5. requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate,所以触发了onDraw,也可能是因为别的原因导致mDirty非空(比如在跑动画)
    6. requestLayout会导致自己以及父族view的PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED标志被设置。
      7.一般来说,只要刷新的时候就调用invalidate,需要重新measure就调用requestLayout,后面再跟个invalidate(为了保证重绘),个人理解。

    感谢:
    https://blog.csdn.net/litefish/article/details/52859300

    https://www.cnblogs.com/ldq2016/p/9035332.html

    相关文章

      网友评论

          本文标题:invalidate和requestLayout之源码区别

          本文链接:https://www.haomeiwen.com/subject/qpuoyhtx.html