Android View requestLayout源码分析

2017-08-24  本文已影响0人  wanderingGuy

我们从view的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;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        //核心代码 调用父view的requestLayout方法
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

viewParent的实例就是View树的ViewRootImpl对象,因此如果在回溯view树过程中没有中断,将会调用它的requestLayout方法。

# ViewRootImpl
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();//核心代码
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //这里需要关注这个runnable对象
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();//遍历起点

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

//这个方法太长了 只调出重点
private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView;
    /** View树遍历的主要三个步骤measure、layout、draw **/  
    ......  
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  
    ......  
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);  
    ......  
    performDraw();  
}

performMeasure、performLayout、performDraw内部依次调用view的绘制流程方法。

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());  
draw(fullRedrawNeeded); 

划重点了,这里的mView就是此ViewRootImpl管理的DecorView。
也就是说调用measure、layout、draw就会触发这个view树的测量、布局和重绘。这部分内容看参看一篇不错的文章
Android视图框架Activity,Window,View,ViewRootImpl理解

何时会触发view树的遍历呢?

注意这里invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了。

一般引起invalidate()操作的函数如下:

  1. 直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
  2. setSelection():请求重新draw(),但只会绘制调用者本身。
  3. setVisibility() :当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
    当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
    同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
  4. setEnabled()方法: 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
  5. dispatchAppVisibility:当应用程序的可见性发生改变时(如启动一个新的Activity),会调用这个函数;一旦ViewRootImpl受到Visibility改变的消息,就会组织一次遍历。
上一篇下一篇

猜你喜欢

热点阅读