Android中View的更新方法:invalidate()和requestLayout()

前言
我们在自定义View时可能需要更新View的显示,比如为View添加动画等等,有两个方法是我们经常会用到的:invalidate()requestLayout(),本文就来具体分析一下这两个方法的区别和使用场景。

在进入正文之前还是要强调一下,本文的分析基于Android 9.0(API Level 28)的源码,不同版本的源码可能会有不同,但是基本思路不会变化太多,可以进行参考。

invalidate()

我们直接来看View的invalidate()方法:

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

可以看出View的invalidate()方法最终会调用invalidateInternal()方法:

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                        boolean fullInvalidate) {
    // ...
    // 判断是否跳过重绘
    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) {
            // 设置PFLAG_INVALIDATED标志位
            mPrivateFlags |= PFLAG_INVALIDATED;
            // 移除PFLAG_DRAWING_CACHE_VALID标志位
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            // damage表示要重绘的区域
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            // 将要重绘的区域传给父View
            p.invalidateChild(this, damage);
        }
        // ...
    }
}

invalidateInternal()方法中首先会根据skipInvalidate()方法判断是否跳过绘制,如果同时满足以下三个条件就直接return,跳过重绘。

  • View是不可见的
  • 当前没有设置动画
  • 父View的类型不是ViewGroup或者父ViewGoup不处于过渡态

接下来根据一系列条件判断是否需要重绘,如果满足以下任意一条就进行重绘。

  • View已经被绘制完成并且具有边界
  • invalidateCache为true并且设置了PFLAG_DRAWING_CACHE_VALID标志位,即绘制缓存可用
  • 没有设置PFLAG_INVALIDATED标志位,即没有被重绘过
  • fullInvalidate为true并且透明度发生了变化

接下来判断如果invalidateCache为true,就给View设置PFLAG_INVALIDATED标志位,这一步很重要,后面还会提到,通过上面的调用也能看出这里的invalidateCache传入的值为true,因此会设置这个标志位。方法的最后会调用mParent即父View的invalidateChild()方法,将要重绘的区域damage传递给父View。下面我们来看ViewGroup的invalidateChild()方法:

@Override
public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // 开启了硬件加速
        onDescendantInvalidated(child, child);
        return;
    }

    ViewParent parent = this;
    if (attachInfo != null) {
        // ...
        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }
            // ...
            parent = parent.invalidateChildInParent(location, dirty);
            // ...
        } while (parent != null);
    }
}

方法内部首先会判断是否开启了硬件加速,接下来我们分别看一下关闭和开启硬件加速情况下的重绘流程。

  • 关闭硬件加速

关闭硬件加速的情况下会循环调用invalidateChildInParent()方法,将返回值赋给parent,当parent为null时退出循环,我们来看ViewGroup的invalidateChildInParent()方法。

@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        // ...
        // 移除PFLAG_DRAWING_CACHE_VALID标志位
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        // ...
        return mParent;
    }
    return null;
}

这里省略了大量代码,主要是对子View传递过来的重绘区域进行运算处理,方法最后会返回mParent。因此在invalidateChild()方法中会通过循环逐层调用父View的invalidateChildInParent()方法,那么当调用到最顶层ViewGroup——DecorView的invalidateChild()方法时,它的mParent是谁呢?我们可以回顾一下ViewRootImpl的setView()方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            // ...
            requestLayout();
            // ...
            view.assignParent(this);
            // ...
        }
    }
}

可以发现方法执行了view.assignParent(this),这里的view其实就是DecorView(ActivityThread的handleResumeActivity()方法中调用wm.addView()传过来的),我们来看一下assignParent()方法,它定义在View中:

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}

很明显assignParent()方法完成了View的成员变量mParent的赋值,因此DecorView的mParent就是上面传入的this,也就是ViewRootImpl。既然清楚了DecorView的mParent,接下来我们就来看一下ViewRootImpl的invalidateChildInParent()方法:

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    // ...
    invalidateRectOnScreen(dirty);
    return null;
}

方法内部首先会调用checkThread()方法:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

checkThread()方法会判断当前线程是否为主线程,如果不是主线程就直接抛出异常,因此我们需要特别注意,invalidate()方法必须在主线程中调用。回到ViewRootImpl的invalidateChildInParent()方法,最后调用了invalidateRectOnScreen()方法,同时由于返回值为null,因此执行完invalidateChildInParent()方法后parent被赋值为null,退出do-while循环。接下来我们就来看一下invalidateRectOnScreen()方法:

private void invalidateRectOnScreen(Rect dirty) {
    // ...
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

invalidateRectOnScreen()方法内部会调用scheduleTraversals()方法,这个方法我们很熟悉了,接下来会调用performTraversals()方法,开始View的三大流程,这里再来回顾一下:

private void performTraversals() {
    // ...
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    if (layoutRequested) {
        // ...
        // 调用performMeasure()方法开始measure流程
        measureHierarchy(host, lp, res,
                desiredWindowWidth, desiredWindowHeight);
    }
    // ...
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        // ...
        // 开始layout流程
        performLayout(lp, mWidth, mHeight);
    }
    // ...
    // 开始draw流程
    performDraw();
    // ...
}

由于此时没有给mLayoutRequested赋值,它的默认值为false,因此不会调用measureHierarchy()performLayout()方法,只调用performDraw()方法,换句话说就是不会执行measure和layout流程,只执行draw流程,接下来就是调用DecorView的draw()方法,遍历DecorView的子View,逐层完成子View的绘制。

  • 开启硬件加速

开启硬件加速时在ViewGroup的invalidateChild()方法中会调用onDescendantInvalidated()方法并直接返回,不会执行后面的invalidateChildInParent()方法,我们来看一下onDescendantInvalidated()方法:

@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    // ...
    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

方法内部会调用mParent的onDescendantInvalidated()方法,和invalidateChildInParent()类似,接下来会逐级调用父View的onDescendantInvalidated()方法,最后来到ViewRootImpl的onDescendantInvalidated()方法。

@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        mIsAnimating = true;
    }
    invalidate();
}

接下来会调用ViewRootImpl的invalidate方法:

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

可以看到这里同样调用了scheduleTraversals()方法,之后的流程和关闭硬件加速的情况类似,同样是调用performDraw()方法,不同的是开启硬件加速的情况下会执行mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback),上一篇文章Android自定义View的基石——View工作原理总结中分析View的draw流程时也介绍过了,这里就不再分析了,之后会依次调用ThreadedRenderer的updateRootDisplayList()updateViewTreeDisplayList()方法。

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;
}

这里需要注意,根据此前的分析,在调用View的invalidate()方法后,会给当前View设置PFLAG_INVALIDATED标志位,因此它的mRecreateDisplayList变量值为true,而其他的父级View由于没有设置PFLAG_INVALIDATED标志位,mRecreateDisplayList值为false。接下来会调用view的updateDisplayListIfDirty()方法,这里的view是DecorView。

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    // ...
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.isValid()
            || (mRecreateDisplayList)) {
        if (renderNode.isValid()
                && !mRecreateDisplayList) {
            // 不需要重新进行绘制
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();

            return renderNode;
        }

        // 需要重新进行绘制
        mRecreateDisplayList = true;

        try {
            // ...
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                // 如果设置了PFLAG_SKIP_DRAW标志位,执行dispatchDraw()方法
                dispatchDraw(canvas);
                drawAutofilledHighlight(canvas);
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().draw(canvas);
                }
                if (debugDraw()) {
                    debugDrawFocus(canvas);
                }
            } else {
                // 没有设置PFLAG_SKIP_DRAW标志位,执行draw()方法
                draw(canvas);
            }
            // ...
        } finally {
            renderNode.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

由于页面首次绘制完成后执行了renderNode.end(canvas),因此这里renderNode.isValid()返回值为true,而DecorView的mRecreateDisplayList值为false,因不会执行后面的重新绘制逻辑,取而代之的是调用dispatchGetDisplayList()方法,我们来看一下这个方法:

@Override
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);
        }
    }
    // ...
}

private void recreateChildDisplayList(View child) {
    child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
    child.mPrivateFlags &= ~PFLAG_INVALIDATED;
    child.updateDisplayListIfDirty();
    child.mRecreateDisplayList = false;
}

dispatchGetDisplayList()方法内部会遍历子View,依次调用recreateChildDisplayList()方法,不难看出recreateChildDisplayList()方法和updateViewTreeDisplayList()方法很像,接下来同样会调用updateDisplayListIfDirty()方法,对于没有设置PFLAG_INVALIDATED标志位的View,它的mRecreateDisplayList值为false,会重复上面的过程,即调用dispatchGetDisplayList()方法;而对于调用了invalidate()方法的View,由于设置了PFLAG_INVALIDATED标志位,它的mRecreateDisplayList值为true,会执行updateDisplayListIfDirty()方法最后的重绘逻辑,即调用dispatchDraw()方法或者draw()方法完成自身及子View的绘制。
最后总结一下invalidate()方法,调用View的invalidate()方法后会逐级调用父View的方法,最终导致ViewRootImpl的scheduleTraversals()方法被调用,进而调用performTraversals()方法。由于mLayoutRequested的值为false,因此不会执行measure和layout流程,只执行draw流程。draw流程的执行过程和是否开启硬件加速有关:

  • 如果关闭了硬件加速,从DecorView开始的所有View都会重新完成绘制
  • 如果开启了硬件加速,只有调用invalidate()方法的View(包括它的子View)会完成重新绘制

由此也可以看出,开启硬件加速确实可以提高重绘的效率。
此外,由于invalidate()方法必须在主线程中调用,那么如果我们想要在子线程中刷新视图要怎么做呢?不用担心,官方还为我们提供了一个postInvalidate()方法,其实从名称上我们也能猜到它的作用了,就是用于在子线程中刷新视图,简单看一下它的定义:

public void postInvalidate() {
    postInvalidateDelayed(0);
}

public void postInvalidateDelayed(long delayMilliseconds) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}

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

哈哈,果然还是用到了Handler,mHandler是ViewRootImpl中的一个成员变量,类型为ViewRootHandler,我们来看一下ViewRootHandler对MSG_INVALIDATE消息的处理:

final class ViewRootHandler extends Handler {
    // ...
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                break;
            // ...
        }
    }
}

可以看出最后还是调用了invalidate()方法,因此postInvalidate()方法其实就是通过Handler完成了线程的切换,使得invalidate()方法在主线程中被调用。

requestLayout()

我们来看View的requestLayout()方法:

public void requestLayout() {
    // ...
    // 给View添加两个标志位
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        // 调用父类的requestLayout()方法
        mParent.requestLayout();
    }
    // ...
}

requestLayout()方法内部会调用mParent即父View的requestLayout()方法,最终会来到ViewRootImpl的requestLayout()方法:

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

首先还是会进行线程的检查,因此requestLayout()方法同样只能在主线程中调用。接着会把mLayoutRequested赋值为true并调用scheduleTraversals()方法。后面的流程相信也不用我多说了,调用performTraversals()方法,由于将mLayoutRequested赋值为true,因此会依次执行measureHierarchy()performLayout()performDraw()方法,开始View的三大流程。
到这里还没完,我们需要探究一下View调用requrestLayout()是否会导致View树中的所有View都进行重新测量、布局和绘制。我们注意到调用requestLayout()方法后,会为当前View及所有父级View添加PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED标志位。首先来回顾一下View的measure()方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // ...
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

    if (forceLayout || needsLayout) {
        // ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        // ...
        // 设置PFLAG_LAYOUT_REQUIRED标志位
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
    // ...
}

可以看出,当View设置了PFLAG_FORCE_LAYOUT标志位后,forceLayout的值为true,因此会执行onMeasure() 方法,而对于没有设置PFLAG_FORCE_LAYOUT标志位的View,需要判断测量尺寸是否发生了改变,如果改变了才会调用onMeasure()方法。在调用onMeasure()方法后会给View设置PFLAG_LAYOUT_REQUIRED标志位,我们再来看View的layout()方法:

public void layout(int l, int t, int r, int b) {
    // ...
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    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);
        // ...
    }
    // ...
}

对于设置了PFLAG_LAYOUT_REQUIRED标志位的View,onLayout()方法肯定会执行,另一种情况就是View的四个顶点坐标发生改变,也会执行onLayout()方法。
结合上面的分析可以得出结论,当View调用了requestLayout()方法后,自身及父级View的onMeasure()onLayout()方法会被调用,对于它的子View,onMeasure()onLayout()方法不一定被调用。
对于draw流程,performDraw()方法会调用ViewRootImpl中的draw()方法:

private boolean draw(boolean fullRedrawNeeded) {
    // ...
    final Rect dirty = mDirty;
    // ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            // ...
            // 开启了硬件加速
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
        } else {
            // ...
            // 关闭了硬件加速
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                    scalingRequired, dirty, surfaceInsets)) {
                return false;
            }
        }
    }
    // ...
    return useAsyncReport;
}

dirty指向ViewRootImpl中的一个成员变量mDirty,类型为Rect,在ViewRootImpl的invalidate()方法中会调用set()方法为其设置四个边界值,由于此时没有调用invalidate()方法,因此mDirty.isEmpty()返回true,不会执行后面的绘制方法,因此整个View树不会进行重新绘制。不过也有这样一种情况,我们知道在执行VIew的layout流程时会调用setFrame()方法,在setFrame()方法中有这样的逻辑:

int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

invalidate(sizeChanged);

可以看出当View的宽或高发生改变时会调用invalidate()方法,导致View的重新绘制。

总结

我们已经分析过了invalidate()requestLayout()的具体实现,现在就来总结一下invalidate()requestLayout()的异同:
相同点
1.invalidate()requestLayout()方法最终都会调用ViewRootImpl的performTraversals()方法。
不同点
1.invalidate()方法不会执行measureHierarchy()performLayout()方法,也就不会执行measure和layout流程,只执行draw流程,如果开启了硬件加速则只进行调用者View的重绘。
2.requestLayout()方法会依次measureHierarchy()performLayout()performDraw()方法,调用者View和它的父级View会重新进行measure、layout,一般情况下不会执行draw流程,子View不一定会重新measure和layout。
综上,当只需要进行重新绘制时就调用invalidate(),如果需要重新测量和布局就调用requestLayout(),但是requestLayout()不保证进行重新绘制,如果要进行重新绘制可以再手动调用invalidate()
光介绍结论可能不是很直观,下面就以一个简单的例子验证一下上面的几个结论,我定义了两个ViewGroup和一个View,代码很简单,如下所示:
MyViewGroup1.java

public class MyViewGroup1 extends ViewGroup {

    public MyViewGroup1(Context context) {
        this(context, null);
    }

    public MyViewGroup1(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewGroup1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setWillNotDraw(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("TAG", "MyViewGroup1执行了onMeasure");
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.e("TAG", "MyViewGroup1执行了onLayout");
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);

            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();

            child.layout(l, t, l + width, t + height);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG", "MyViewGroup1执行了onDraw");
        canvas.drawColor(Color.YELLOW);
    }
}

MyViewGroup2.java

public class MyViewGroup2 extends ViewGroup {

    public MyViewGroup2(Context context) {
        this(context, null);
    }

    public MyViewGroup2(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewGroup2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setWillNotDraw(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("TAG", "MyViewGroup2执行了onMeasure");
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.e("TAG", "MyViewGroup2执行了onLayout");
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);

            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();

            child.layout(l, t, l + width, t + height);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG", "MyViewGroup2执行了onDraw");
        canvas.drawColor(Color.GREEN);
    }
}

MyView.java

public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("TAG", "MyView执行了onMeasure");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e("TAG", "MyView执行了onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG", "MyView执行了onDraw");
        canvas.drawColor(Color.RED);
    }
}

为了保证ViewGroup的onDraw()方法执行,我在构造方法中调用了setWillNotDraw(false)。布局文件也很简单,一个三级的嵌套:
activity_test.xml

<?xml version="1.0" encoding="utf-8"?>
<com.example.MyViewGroup1 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/myViewGroup1"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.MyViewGroup2
        android:id="@+id/myViewGroup2"
        android:layout_width="300dp"
        android:layout_height="300dp">

        <com.example.MyView
            android:id="@+id/myView"
            android:layout_width="200dp"
            android:layout_height="200dp" />

    </com.example.MyViewGroup2>

</com.example.MyViewGroup1>

运行后的效果如下:

我给三个View添加了点击事件,点击后分别调用自身的invalidate()requestLayout()方法,下面分情况看一下。

  • 调用invalidate()方法

开启硬件加速
调用MyView的invalidate()方法:

调用MyViewGroup2的invalidate()方法:

调用MyViewGroup1的invalidate()方法:

关闭硬件加速
调用MyView的invalidate()方法:

调用MyViewGroup2的invalidate()方法:

调用MyViewGroup1的invalidate()方法:

可以看出,关闭硬件加速时,调用任何一个View的invalidate()方法都会导致整个View树的重新绘制;开启硬件加速时,调用哪一个View的invalidate()方法就会重绘哪一个View。invalidate()方法不会导致onMeasure()onLayout()被调用。

  • 调用requestLayout()方法

开启硬件加速
调用MyView的requestLayout()方法:

调用MyViewGroup2的requestLayout()方法:

调用MyViewGroup1的requestLayout()方法:

关闭硬件加速
调用MyView的requestLayout()方法:

调用MyViewGroup2的requestLayout()方法:

调用MyViewGroup1的requestLayout()方法:

可以看出此时是否开启硬件加速对于requestLayout()方法的调用流程没有影响,调用View的requestLayout()方法会导致自身及其父View的onMeasure()onLayout()方法被调用,并不会调用onDraw()方法进行重绘,当然前面也分析过了,onDraw()方法不是一定不会被调用,当View重新绘制时硬件加速的作用就会有所体现了。
其实这篇文章本来是作为上一篇文章Android自定义View的基石——View工作原理总结的一个章节的,由于字数限制只能单独提出来了,因此难免有地方介绍得不是很详细,可以结合上一篇文章来看。由于自身水平的原因,文章中分析得不正确的地方,欢迎大家交流指出,

参考文章

invalidate、postInvalidate与requestLayout浅析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容