Android invalidate、requestLayout

本文详细解析了Android中`invalidate()`和`requestLayout()`的方法原理及流程。`invalidate()`用于触发视图重绘,适用于显示内容变化的情况,而`requestLayout()`则涉及视图的测量和布局更新,适用于尺寸或位置变化。两者都会向上遍历视图树,但`requestLayout()`会执行测量和布局流程,`invalidate()`仅执行绘制流程。整个过程涉及ViewRootImpl,通过设置不同的标记位控制视图的更新。

1.view.invalidate()

invalidate是废弃、使无效的意思。Android中需要重绘某个视图时就可以调用该函数,表示view的某个显示区域内容变脏了,该显示区域需要被重新绘制。

view.invalidate()方法用来完成UI的刷新,它可以重新触发一次View的绘制流程。绘制过程一般不会对所有视图进行重绘,而仅绘制那些“需要重绘”的视图,也就是mPrivateFlags中包含标志位为DRAWN的视图(当某视图需要绘制时,就给它的mPrivateFlags中添加该标志位)。

函数invalidate()会根据所有视图中的DRAWN标志位计算具体哪个区域需要重绘,这个区域将用一个矩形Rect表示,并最终将这个Rect存放到ViewRoot中的mDirty变量中,之后的重绘过程将重绘所有包含在该mDirty区域中的视图。

注意: invalidate方法必须在主线程中才能调用。如果需要在非UI线程中使用,可以调用view.postInvalidate() 方法。

invalidate方法和postInvalidate方法都用于View的刷新,invalidate方法应用在UI线程中,而postInvalidate方法应用在非UI线程中,用于将线程切换到UI线程,postInvalidate方法最后调用的也是invalidate方法。

invalidate()方法用来刷新重绘当前的View,如果当前View的布局尺寸、位置没有变化,仅仅是绘制内容变化了,那么就可以调用invalidate()方法。

 

2.invalidate刷新流程

Android中View是以树形结构组织的,如果红色的View调用invalidate(),看一下整个流程:

d2a48b5b20a149fda6a16d5256f585e9.png

某个view调用invalidate()后,如果这个View可见,那么会使整个View视图无效,然后在未来的某个时间点View的onDraw()方法将被调用。

①View.invalidate()

View.class:

public void invalidate() {

    invalidate(true);

}

public void invalidate(boolean invalidateCache) {

    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);//传入当前view的位置参数

}

invalidateInternal方法的参数:

l,t,r,b是View的大小;

invalidateCache: 设置View的缓存是否失效,通常情况下是ture,当View的大小改变时为false;

fullInvalidate: 默认为 true。

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {

    if(skipInvalidate()) { //判断该子View是否可见或者是否处于动画中。如果子View不可见或者没有处于动画中,则不让该子View失效,即该子View不会被重绘       

        return;

    }

    ……

    // 根据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) { //默认为true

            mLastIsOpaque = isOpaque();

            mPrivateFlags &= ~PFLAG_DRAWN; //清除绘制标记

        }

       // 需要绘制,设置PFLAG_DIRTY标记位

        mPrivateFlags |= PFLAG_DIRTY;

        if (invalidateCache) {

            mPrivateFlags |= PFLAG_INVALIDATED;

            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

        }

        final AttachInfo ai = mAttachInfo;

        final ViewParent p = mParent; //子View的ViewParent就是它的父View,即ViewGroup

        if (p != null && ai != null && l < r && t < b) {

            // 记录需要重新绘制的区域damge,该区域为该View的尺寸

            final Rect damage = ai.mTmpInvalRect;

            damage.set(l, t, r, b);

            //p为该view的父布局,调用父布局的invalidateChild()

            p.invalidateChild(this, damage);

        }

       ...

    }

}

首先判断该子View是否可见或者是否处于动画中,如果子View不可见或者没有处于动画中,则不让该子View失效,即该子View不会被重绘。

然后根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘。

最后调用父类ViewParent的invalidateChild()方法,子View的ViewParent就是它的父View即ViewGroup。将View需要绘制大小Rect告诉父ViewGroup,并调用父ViewGroup的invalidateChild(),其中damage变量表示需要进行重绘的区域,后面在一系列的调用过程中会不断根据父布局来调整这个绘制区域。

②ViewGroup.invalidateChild()

ViewGroup.class:

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; //parent为当前的ViewGroup

    if (attachInfo != null) {

        ...

        //从当前View向上不断遍历当前View的父布局,最后遍历到ViewRootImpl

        do {

            ...

         //在do while循环中为每个父布局设置标记位

            if (view != null) { //父视图不为空

                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {

                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY; //设置父类的mPrivateFlags标记位

                }

           }

           parent = parent.invalidateChildInParent( location, dirty); //在do while中循环调用父布局的invalidateChildInParent方法

     //在do while中循环为每个父布局设置dirty区域

           if(view != null) {

                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.floor(boundingRect.right),  (int)Math.floor(boundingRect.bottom));//父布局中同样也设置dirty区域

                }

            }

        } while (parent != null);

    }

}

绘制分为两个分支:硬件加速绘制和软件绘制,这里主要分析软件绘制。

软件绘制会循环一直到根的DecorView中,DecorView是由ViewRootImp管理,并维护mPrivateFlags。(mPrivateFlags用于计算需要刷新的View。)

在ViewGroup的invalidateChild方法中有一个循环,循环里面一直调用父布局ViewGroup的invalidateChildInParent方法,而View和ViewGroup的最终父布局都是ViewRootImpl。

所以,当调用View的invalidate方法后,View会不断向上调用父布局ViewGroup的invalidateChildInParent绘制方法,并在这个过程中计算需要重绘的区域,最终调用过程会走到ViewRootImpl中调用ViewRootImpl的invalidateChildInParent方法,在其中执行performTraversals重绘操作。(注意这个do while 循环中有两个invalidateChildInParent方法,一个是ViewGroup的,一个是ViewRootImpl的)。

4bace3eb85c6471b80962b6027395300.png

内层的parent是调用的ViewGroup的invalidateChildInParent方法。

最外层的View,即DecorView,也就是调用DecorView的ViewParent#invalidateChildInParent方法。而DecorView的ViewParent就是ViewRootImpl。理由如下:

在Activity中调用setContentView()方法后,经过installDecor() -> generateLayout(mDecor) -> mLayoutInflater.inflate(layoutResID, mContentParent)等方法调用后,Activity的布局文件就已成功添加到了DecorView的mContentParent中,此时DecorView还未添加到Window中。

Activity调用onResume方法,然后调用makeVisible方法后,DecorView才被添加到Window中。

这里涉及到Activity的启动过程,简单写一下,顺序调用ActivityThread的handleLauncheActivity,handleResumeActivity方法,handleResumeActivity方法中首先调用performResumeActivity方法,performResumeActivity方法中调用Activity中的performResume方法,之后调用Activity的onResume方法,最后调用Activity的makeVisible方法,makeVisible方法中会把当前的顶层DecoView通过WindowManager的addView方法添加到WindowManager中,而WindowManager的实现类WindowManagerImpl中调用的是WindowManagerGlobal的addView方法。

ViewRootImpl#setView()方法,其中的参数view即为DecorView,在该方法里面调用了view.assigenParent(this),把ViewRootImpl设置为DecorView的ViewParent。

所以,在do while循环中有两个invalidateChildInParent方法,一个是ViewGroup里的,一个是ViewRootImp里的。

③ViewGroup.invalidateChildInParent()

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {

    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {

        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != FLAG_OPTIMIZE_INVALIDATE) {

            // 调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标(保存子view的left和top)

            dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY);

            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {

                 // 调用union方法,把子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();

                }

            }

            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 {

                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);

            }

            location[CHILD_LEFT_INDEX] = mLeft;

            location[CHILD_TOP_INDEX] = mTop;

            mPrivateFlags &= ~PFLAG_DRAWN;

        }

        //返回当前View的父容器,以便进行下一次循环

        return mParent;

    }

    return null;

}

ViewGroup的invalidateChildInParent方法有两个参数,location数组为自身左边、上边距离父组件的距离,确立了在坐标系的相对位置;dirty表示一个包含自身宽高的矩形面积,确立了需要重绘的面积大小,这两个参数告知了在屏幕的具体哪个区域是需要重绘的。在方法内部调用offset方法,把当前dirty区域的坐标转化为父容器中的坐标。接着调用union方法,把子dirty区域与父容器的区域求并集,换句话说,dirty区域变成父容器区域。最后返回当前视图的父容器,以便进行下一次循环。

ViewGroup#invalidateChild()方法里面的do while循环完最终会调用最外层ViewRootImpl里面的invaludateChildInParent方法。

④ViewRootImp.invalidateChildInParent()

ViewRootImp.class:

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {

    checkThread();   

    //首先把需要更新的区域计算出来

    if (dirty == null) { //如果dirty为null就表示要重绘当前ViewRootImpl指示的整个区域

        invalidate();

        return null;

    } else if (dirty.isEmpty() && !mIsAnimating) { //如果dirty为empty则表示经过计算需要重绘的区域不需要绘制

        return null;

    }

    ……

    //然后调用invalidateRectOnScreen方法绘制

    invalidateRectOnScreen(dirty);

    return null;

}

在根布局ViewRootImp中会处理传入的Rect dirty区域。该方法主要还是dirty区域的计算。然后调用ViewRootImpl的invalidateRectOnScreen(Rect dirty)方法。

private void invalidateRectOnScreen(Rect dirty) {

    final Rect localDirty = mDirty;

    // 通过传入的区域,计算需要更新的区域

    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);

    //判断要重绘的区域是不是在屏幕内,也就是判断dirty和屏幕区域是否有交叉,屏幕外的就不用绘制了

    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();

    }

}

该方法主要是刷新屏幕上面的一个Rect区域,而Rect区域就是调用invalidate方法的那个View大小。然后调用 scheduleTraversals()方法进行绘制。

void scheduleTraversals() {

    if (!mTraversalScheduled) { //注意这个标志位,防止多次调用requestLayout,只有当这个标志位为false时才有效        

        mTraversalScheduled = true;

        mTraversalBarrier = mHandler.getLooper( ).getQueue().postSyncBarrier(); //通过postSyncBarrier()设置Handler消息的同步屏障 

        //关键在这里!Choreographer通过postCallback提交一个任务,mTraversalRunnable是要执行的回调,有了同步屏障mTraversalRunnable就会被优先执行          

        mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

        if (!mUnbufferedInputDispatch) {

            scheduleConsumeBatchedInput();

        }

        notifyRendererOfFramePending();

        pokeDrawLockIfNeeded();

    }

}

scheduleTraversals方法中通过Handler开启同步屏障,然后调用了mChoreographer.postCallback方法。

Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable.

ViewRootImpl.class:

final class TraversalRunnable implements Runnable {

    @Override

    public void run() {

        doTraversal();

    }

}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

调用doTraversal()方法开始绘制。

scheduleTraversals()这个方法很关键,会通过一系列的调用最终调用TraversalRunnable.run()方法,然后调用doTraversal()方法,doTraversal()中调用performTraversals()。

void doTraversal() {

    if (mTraversalScheduled) {

        mTraversalScheduled = false;

        mHandler.getLooper().getQueue().remov eSyncBarrier(mTraversalBarrier); //移除同步屏障

        performTraversals(); //这里就是开始绘制

    }

}

void performTraversals() {

    if (layoutRequested) {

        mLayoutRequested = false;

    }

    layoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 更新window

    ...

    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    ...

   performLayout(lp, mWidth, mHeight);

    ...

    performDraw();

    ...

}

performTraversals()依次执行performMeasure、performLayout、performDraw()。

其中mLayoutRequested默认为 false,意味着只会执行onDraw(),不会执行onMeasure()。

private void performDraw() {

    boolean canUseAsync = draw( fullRedrawNeeded); //执行draw方法

}

draw方法的实现为:

private boolean draw(boolean fullRedrawNeeded) {

    final Rect dirty = mDirty;

    if(fullRedrawNeeded){ //是否需要重新draw

        dirty.set(0, 0, (int)(m width * appScale + 0.5f), (int)(mHeight * appScale + 0.5f));

    }

    mAttachInfo.mTreeObserver.dispatchOnDraw( );

    //软件绘制的入口

    if(!drawSoftware(surface, mAttachInfo, xoffset, yoffset, scalingRequired, dirty, surfaceInsets)) {

        return false;

    }

}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets){

    Canvas canvas = mSurface.lockCanvas(dirty); //通过surface得到画布canbas

    ……

    mView.draw(canvas); //通过mView.draw在canvas上绘制整个控件树(mView是DecorView)

}

到这里,调用到了View的draw方法

④View.draw()

public void draw(Canvas canvas) {

    drawBackground(canvas); //绘制背景

    ……

    onDraw(canvas);//绘制控件自身的内容(每个控件继承自View时,都会重写onDraw方法,这里就是调用重写的onDraw方法)

    dispatchDraw(canvas); //给子view分发,如果子view的设置了标记位,就会触发onDraw

    ……

}

performDraw方法中调用ViewRootImpl#draw()方法,ViewRootImpl#draw()方法又调用ViewRootImpl#drawSoftware()方法,ViewRootImpl#drawSoftware()方法中,当前的View调用了View#draw() 方法,即mView.draw(canvas)对当前的View进行绘制。

现在回忆一下整个invalidate的过程:沿着viewTree从叶子节点view开始一直向上找到父view,在这个过程中计算都有哪些控件会受到影响,并把它们受影响的区域和flag标记出来,然后对根布局进行重新绘制,根布局绘制时会通过dispatchDraw把draw分发给子控件,这样就可以把viewTree上有标记的view全部重新绘制一遍。

ViewRootImpl#performTraversals()方法依次可能会调用performMeasure,performLayout,performDraw。这三个方法最终也会调用常用的onXXX方法。但在这里这三个方法不一定都会调用,当调用invalidate的时候,这个时候只会调用到performDraw方法;如果view位置发生改变了,则也会调用到performLayout方法;如果大小也改变了,则也会调用perforMeasure方法。这三个方法就会回调View里面的mesure,layout,draw方法,measure内部会回调onMeasure,layout内部会回调onLayout,draw内部会回调onDraw。

至此,整个invalidate()流程结束,贯穿着一个布局或View的所有子view,以下是流程图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

2b3c793a958b437599a90213d72eb6dd.jpg

 总结:Android中UI刷新的逻辑都是按照树形结构来运行的,从子View把要做的事情传给根布局,再由根布局分布整个View Tree , 导致整个View Tree进行刷新。

所以,在普通的view里面,通过调用invalidate方法来注册vsync信号,开始一帧画面的绘制,调用到onDraw方法。

a83dd6d74ca84446b5d5e0ba29402f7b.png

 ①首先invalidate调用view.invalidateInternal传入当前view的位置参数。

②在invalidateInternal中判断当前view的状态,是否需要重绘,然后通过父View的invalidateChild方法,将需要重绘的区域传递给父View。

③在invalidateChild中先是设置了需要重绘的区域dirty,之后再do…while中,反复的调用parent = parent.invalidateChildInParent()方法,来调用父类的invalidateChildInParent对View的重绘请求进行传递。

④然后在ViewGroup.invalidateChildInParent中使用offset,把子View需要重绘的坐标区域转换为父View中的坐标区域。之后使用union对子View与父View的区域进行集合运算,获得需要绘制的区域。

⑤在ViewRootImpl.invalidateInparent中,进入之后会线程以及重绘区域的检查,之后调用invalidateRectOnScreen方法,然后调用scheduleTraversals()方法。

⑥ViewRootImpl.scheduleTraversals里面,将会之后handler,之后会调用mTraversalRunnable类,从而调用doTraversal方法,最后调用performTraversals()执行ViewTree的遍历。

⑦ViewRootImpl.performTraversals()中,在其中进行View的是否可见,是否为surfasce,是否正在绘制,是否存在于删除列表中等判断,之后调用performDraw()开始执行绘制。在performDraw()又调用了ViewRootImpl的draw方法,并传递了fullRedrawNeeded参数,此参数源自mFullRedrawNeeded成员变量,用于表示是否需要重新绘制全部的View。

⑧然后就会调用ViewRootImpl.draw,在draw方法中,根据传如fullRedrawNeeded参数,设置需要重绘的dirty区域,最后调用drawSoftware方法,把参数传递进去。

⑨最后ViewRootImpl.drawSoftware中,首先对canvas进行一些属性设置,包括色块、平移等。之后调用mView.draw(canvas)方法,开始对View进行绘制。

 

invalidate()性能考虑:

子View将更新逻辑传递给ViewRootImp过程中,通过Rect计算需要更新的View,并用mPrivateFlags标记出来。

View从上往下执行onMeasure()、onLayout()、onDraw()时通过mPrivateFlags判断是否刷新达到优化的目的。

 

invalidate只是调用performDraw():

onMeasure()、onLayout()、onDraw() 这三个函数不一定都执行,由 mLayoutRequested默认是false仅会执行onDraw() 。

如果发现onMesure()、onLayout()无效时候,通常会调用requestLayout(),本质上它是将mLayoutRequested设置为true。

 

3.View.postInvalidate()

在子线程中调用。

public void postInvalidate() {

    postInvalidateDelayed(0);

}

public void postInvalidateDelayed(long delayMilliseconds) {

    final AttachInfo attachInfo = mAttachInfo;

    if (attachInfo != null) {

            attachInfo.mViewRootImpl.dispatchInvali dateDelayed(this, delayMilliseconds);

    }

}

postInvalidate方法最后调用了ViewRootImpl的dispatchInvalidateDelayed方法。

final ViewRootHandler mHandler = new ViewRootHandler();

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {

    Message msg = mHandler.obtainMessage( MSG_INVALIDATE, view);

    mHandler.sendMessageDelayed(msg, delayMilliseconds);

}

ViewRootImpl中的dispatchInvalidateDelayed方法就是向ViewRootHandler发送了一个MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一个内部Handler类。

ViewRootHandler.java:

final class ViewRootHandler extends Handler {

    @Override

    public String getMessageName(Message message) {

        switch (message.what) {

            case MSG_INVALIDATE:

                return "MSG_INVALIDATE";

            ...

        }

        return super.getMessageName(message);

    }

    ...

    @Override

    public void handleMessage(Message msg) {

        switch (msg.what) {

        case MSG_INVALIDATE:

            ((View) msg.obj).invalidate();

            break;

        ...

        }

    }

ViewRootHandler中对于MSG_INVALIDATE消息的处理就是调用的View的invalidate方法。

ViewRootImpl是在UI线程的,所以postInvalidate方法的作用就是将非UI线程的刷新操作切换到UI线程,以便在UI线程中调用invalidate方法刷新View。

所以如果自定义的View本身就是在UI线程,没有用到多线程的话,直接用invalidate方法来刷新View就可以了。

 

4.requestLayout

View.class:

public void requestLayout() {

    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {

        ViewRootImpl viewRoot = getViewRootImpl();

        if (viewRoot != null && viewRoot.isInLayout()) {

            if (!viewRoot.requestLayoutDuringLayout( this)) {

                return;

            }

        }

        mAttachInfo.mViewRequestingLayout = this;

    }

    //设置PFLAG_FORCE_LAYOUT标记位,这样就会导致重新测量和布局

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;

    //设置PFLAG_INVALIDATED就会进行重新绘制

    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {

        //不断调用上层View的requestLayout方法

        mParent.requestLayout();

    }

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {

        mAttachInfo.mViewRequestingLayout = null;

    }

}

requestLayout方法中会设置PFLAG_FORCE_LAYOUT标记,最终调用的是ViewRootImpl中的requestLayout方法。

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

ViewRootImpl.class:

public void requestLayout() {

    if (!mHandlingLayoutInLayoutRequest) {

        checkThread(); //检查是否是UI线程

        mLayoutRequested = true; //将mLayoutRequested设置为true

        scheduleTraversals();

    }

}

ViewRootImpl中的requestLayout方法中会调用scheduleTraversals方法,这是一个异步方法,最终会调用到ViewRootImpl#performTraversals方法,这也是View工作流程的核心方法,在这个方法内部分别调用measure、layout、draw方法来进行View的三大工作流程。

先看View#measure方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

    ...

    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    ...

    if (forceLayout || needsLayout) {

        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);

        if (cacheIndex < 0 || sIgnoreMeasureCache) {

            //调用onMeasure方法

            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;

        }

        //设置PFLAG_LAYOUT_REQUIRED标记位,用于layout方法

        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;

    }

    ...

}

首先是判断一下标记位,如果当前View的标记位为PFLAG_FORCE_LAYOUT,那么就会进行测量流程,调用onMeasure方法对该View进行重新测量,在measure方法中最后设置了PFLAG_LAYOUT_REQUIRED标记位,这个标记位的作用就是在View的layout流程中,如果当前View设置了该标记位,则会进行布局流程。这样在layout方法中就会执行onLayout方法进行布局流程。

public void layout(int l, int t, int r, int b) {

    ...

    //判断标记位是否为PFLAG_LAYOUT_REQUIRED,如果有则对该View进行布局(由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以会进入调用onLayout方法进行布局流程)

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

        onLayout(changed, l, t, r, b);

        //取消PFLAG_LAYOUT_REQUIRED标记位

        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;

        if (li != null && li.mOnLayoutChangeListeners != null) {

            ArrayList<OnLayoutChangeListener> listenersCopy =  (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();

            int numListeners = listenersCopy.size();

            for (int i = 0; i < numListeners; ++i) {

                listenersCopy.get(i).onLayoutChange( this, l, t, r, b, oldL, oldT, oldR, oldB);

            }

        }

    }

    //取消PFLAG_FORCE_LAYOUT标记位

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;

    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

}

由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以在layout方法中onLayout方法会被调用执行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED标记位。

由于在measure方法中的最后给mPrivateFlags变量添加了PFLAG_LAYOUT_REQUIRED标志位,所以在接下来的layout方法中,if判断的条件才会成立。如果在measure方法中没有添加这个标志位(或者说view没有执行过requestLayout方法),那么layout就会被直接跳过,也就不会执行onLayout。

82cc8e94a789409fac300b7150e051cd.jpg

比如这个布局,当调用TextView的requestLayout方法,首先会给TextView添加标志位,然后找到自己的parent的 RelativeLayout relativeLayout1,然后调用relativeLayout1的requestLayout方法,它也会给自己添加标志位,重复往上递归...。直到ViewRootImpl的requestLayout,最后整个视图树一步步向下递归重绘的时候,发现relativeLayout1有标志位,而relativeLayout2没有标志位,所以整个流程会relativeLayout1重新测量和布局,relativeLayout2不会。

总结:

①requestLayout方法会标记PFLAG_FORCE_LAYOUT,然后一层层往上调用父布局的requestLayout方法并标记PFLAG_FORCE_LAYOUT,最后调用ViewRootImpl中的requestLayout方法开始View的三大流程,然后被标记的View就会进行测量、布局和绘制流程。

②invalidate方法的过程和requestLayout方法很像,但是invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法,不会调用onMeasure和onLayout方法。

最后以一幅流程图来说明requestLayout、invalidate的区别:

733f2a5633be468bbdad8c7ef2991a37.webp

 requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用;invalidate方法则只会导致View的onDraw方法被调用。

一般来说,如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局、绘制这三个流程,往往使用requestLayout。而invalidate则是刷新当前View,使当前View进行重绘,不会进行测量、布局流程,因此如果View只需要重绘而不需要测量,布局的时候,使用invalidate方法往往比requestLayout方法更高效。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值