美文网首页
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