美文网首页
invalidate和requestLayout区别

invalidate和requestLayout区别

作者: 风月寒 | 来源:发表于2021-05-14 14:18 被阅读0次
    invalidate
    public void invalidate() {
            invalidate(true);
    }
    public void invalidate(boolean invalidateCache) {
            invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }
    

    最终调用的是invalidateInternal()。

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                boolean fullInvalidate) {
            if (mGhostView != null) {
                mGhostView.invalidate(true);
                return;
            }
    
            if (skipInvalidate()) { //1
                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;
                }
    
                
                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);//2
                }
                ......
            }
        }
    

    1处首先会通过 skipInvalidate 方法判断是否要跳过 invalidate 过程,如果同时满足以下条件则跳过:

    View 不可见

    当前没有运行动画

    父 View 不是 ViewGroup 类型或者父 ViewGoup 不处于过渡态

    接下来再判断是否需要重绘,如果满足以下任意一个条件则进行重绘:

    View 已经绘制完成且具有边界
    invalidateCache == true 且设置了 PFLAG_DRAWING_CACHE_VALID 标志位,即绘制缓存可用没有设置 PFLAG_INVALIDATED 标志位,即没有被重绘过
    fullInvalidate == true 且在 透明 和 不透明 之间发生了变化

    在处理了一些标志位的逻辑后调用了父 View 的 invalidateChild 方法并将要重绘的区域 damage 传给父 View。于是接着看 2处。

    2处一直往上调,最终会调到ViewRootImpl的invalidateChild()。

    ViewRootImpl
    
    public void invalidateChild(View child, Rect dirty) {
            invalidateChildInParent(null, dirty);
        }
        
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
            //由前面可知,dirty != null且不为空
            if (dirty == null) {
                invalidate();
                return null;
            } else if (dirty.isEmpty() && !mIsAnimating) {
                return null;
            }
            ......
    
            invalidateRectOnScreen(dirty);
    
            return null;
        }
    private void invalidateRectOnScreen(Rect dirty) {
            final Rect localDirty = mDirty;
    
            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();
            }
        }
    

    先经过view的一层层调用,最后一直调用到ViewRootImpl的scheduleTraversals()。我们都知道,该方法最终会调用performTraversals(),而view的三大绘制流程就是在performTraversals()。其调用链为:scheduleTraversals()--》mTraversalRunnable--》doTraversal()--》performTraversals()。

    我们都知道view的绘制流程如下:

    performMeasure()--》measure()--》onMeasure()

    performLayout() --> layout() --> onLayout()

    performDraw() --> draw() --> onDraw()/dispatchDraw()

    private void performTraversals() {
    
        //measure
        ......
         boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
            if (layoutRequested) {
                ......
                windowSizeMayChange |= measureHierarchy(host, lp, res,
                        desiredWindowWidth, desiredWindowHeight);
            }
        ......
        
        if (!mStopped || mReportNextDraw) {
            ......
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                updatedConfiguration) {
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
            ......
        }
        
        //layout
        ......
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
            boolean triggerGlobalLayoutListener = didLayout
                    || mAttachInfo.mRecomputeGlobalAttributes;
            if (didLayout) {
                performLayout(lp, mWidth, mHeight);
            }
            
        
        //draw
        ......
        
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    
            if (!cancelDraw) {
                ......
                performDraw();
        
            } 
        .....
    }
    
    

    上面是performMeasure,performLayout,performDraw三个流程的简化代码以及判定的条件。

    首先我们来看走performMeasure()的条件:measureHierarchy()里面最终调用的是performMeasure()。

    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    

    mLayoutRequested我们全局搜索发现在requestlayout()进行了赋值。而调用invalidate的时候没有对这个变量赋值,所以为false。

    所以invalidate()不会走measureHierarchy()。所以在第一个判定条件下不会走performMeasure。

    继续看第二个。

    !mStopped || mReportNextDraw
    

    mStopped与activity的生命周期有关,对应onStop,很明显mStopped == false,所以该判定条件为true。而第二个if,而只有满足上面的条件 —— mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 才会调用 performMeasure 。

    didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    

    由前面可知layoutRequested为false,所以不会走performLayout()。

    cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    
    public final boolean dispatchOnPreDraw() {
            boolean cancelDraw = false;
            final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
            if (listeners != null && listeners.size() > 0) {
                CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
                try {
                    int count = access.size();
                    for (int i = 0; i < count; i++) {
                        cancelDraw |= !(access.get(i).onPreDraw());
                    }
                } finally {
                    listeners.end();
                }
            }
            return cancelDraw;
        }
    

    由dispatchOnPreDraw()的注释可以知道,如果当前绘制应该被取消和重新调度,则为True,否则为false。

    isViewVisible对应是否可见,所以可以得到cancelDraw为false。

    则会走performDraw().

    requestLayout
    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;
            }
    
            mPrivateFlags |= PFLAG_FORCE_LAYOUT;//1
            mPrivateFlags |= PFLAG_INVALIDATED;
    
            if (mParent != null && !mParent.isLayoutRequested()) {
                mParent.requestLayout();
            }
            if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
                mAttachInfo.mViewRequestingLayout = null;
            }
        }
    

    这里重点看注释1,在后面有用。然后调用mParent.requestLayout(),和invalidate一样,一层层的调用,在ViewGroup没有实现这方法,所以直接看ViewRootImpl的requestLayout().

     public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    

    在requestLayout()里面先先检查线程,然后重点来了,将mLayoutRequested进行赋值,在介绍invalidate的时候我们知道,这个值影响是否进行performMeasure(),performLayout()的调用。然后又遇到这个熟悉的方法scheduleTraversals()。

    private void performTraversals() {
    
        //measure
        ......
         boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
            if (layoutRequested) {
                ......
                windowSizeMayChange |= measureHierarchy(host, lp, res,
                        desiredWindowWidth, desiredWindowHeight);
            }
        ......
        
        if (!mStopped || mReportNextDraw) {
            ......
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                updatedConfiguration) {
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
            ......
        }
        
        //layout
        ......
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
            boolean triggerGlobalLayoutListener = didLayout
                    || mAttachInfo.mRecomputeGlobalAttributes;
            if (didLayout) {
                performLayout(lp, mWidth, mHeight);
            }
            
        
        //draw
        ......
        
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    
            if (!cancelDraw) {
                ......
                performDraw();
        
            } 
        .....
    }
    
    

    因为mLayoutRequested为true,mStopped为false,则!mStopped为true。则layoutRequested为true。讲一下mReportNextDraw这个值,默认为false,可以通过setReportNextDraw()进行赋值。

    public void setReportNextDraw() {
            reportNextDraw();
            invalidate();
    }
    private void reportNextDraw() {
        if (mReportNextDraw == false) {
            drawPending();
        }
        mReportNextDraw = true;
    }
    

    然后进入measureHierarchy().

    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
                final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
            int childWidthMeasureSpec;
            int childHeightMeasureSpec;
            boolean windowSizeMayChange = false;
    
            if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                    "Measuring " + host + " in display " + desiredWindowWidth
                    + "x" + desiredWindowHeight + "...");
    
            boolean goodMeasure = false;
            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                final DisplayMetrics packageMetrics = res.getDisplayMetrics();
                res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
                int baseSize = 0;
                if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                    baseSize = (int)mTmpValue.getDimension(packageMetrics);
                }
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                        + ", desiredWindowWidth=" + desiredWindowWidth);
                if (baseSize != 0 && desiredWindowWidth > baseSize) {
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        goodMeasure = true;
                    } else {
                        baseSize = (baseSize+desiredWindowWidth)/2;
                        if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                                + baseSize);
                        childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                     ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                            if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                            goodMeasure = true;
                        }
                    }
                }
            }
    
            if (!goodMeasure) {
                childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                    windowSizeMayChange = true;
                }
            }
            return windowSizeMayChange;
        }
    

    对于这个方法可以重点看这篇文章 https://zhuanlan.zhihu.com/p/360405914

    在这个方法里面会调用performMeasure();

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
            if (mView == null) {
                return;
            }
            try {
                mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    然后调用measure()。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            boolean optical = isLayoutModeOptical(this);
           
            long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
            if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
    
            final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;//2
            ......
            if (forceLayout || needsLayout) {
                // first clears the measured dimension flag
                mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    
                resolveRtlPropertiesIfNeeded();
    
                int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
                if (cacheIndex < 0 || sIgnoreMeasureCache) {
        
                    onMeasure(widthMeasureSpec, heightMeasureSpec);
                    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                } else {
                    long value = mMeasureCache.valueAt(cacheIndex);
                
                    setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                    mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                }
    
    
                mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;//3
            }
    
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
    
            mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                    (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
        }
    

    因为在前面的注释1可知,mPrivateFlags设置为PFLAG_FORCE_LAYOUT,所以forceLayout为true.所以cacheIndex = -1,进入下面的那个if,从而走onMeasure(),然后将mPrivateFlags置为PFLAG_LAYOUT_REQUIRED。

    然后第二步performlayout也会进去。

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
            mLayoutRequested = false;
            mScrollMayChange = true;
            mInLayout = true;
    
            try {
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
                mInLayout = false;
                ......
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            mInLayout = false;
        }
    

    performLayout中会调用layout();

    public void layout(int l, int t, int r, int b) {
            ......
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b);
                .......
            }
            mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
            mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
            ......
        }
    

    在上面得注释3可以知道,走完onMeasure(),将mPrivateFlags置为PFLAG_LAYOUT_REQUIRED,所以changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED为true,然后走onLayout()。

    最后吧PFLAG_FORCE_LAYOUT这个标志取消。所以走onMeasure一定会走onLayout。然后onDraw()的流程跟上面invalidate()分析的一样。但是很多的文献都说requestLayout不会走onDraw(),但是经过分析和测试打印,是会走onDraw()的。

    文献引用:

    https://zhuanlan.zhihu.com/p/360405914

    相关文章

      网友评论

          本文标题:invalidate和requestLayout区别

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