View&ViewGroup的绘制机制

作者: 埃赛尔 | 来源:发表于2017-12-21 18:13 被阅读45次

    写在前面的话

    一直对View&ViewGroup没有太多的了解,借此机会系统的认识下我们经常用却不了解的View
    说起view绘制的起始分两种情况:
    1 第一次填充ui的时候在 即setContentView()中
    2 当View&ViewGroup内容改变的时候
    先说Acitivity的setContentView()吧:

     getWindow().setContentView(layoutResID);
    

    这里的Window是PhoneWindow:

    if (mContentParent == null) {
    //如果window没有根布局,则为其创建并把这个根布局添加到DecorView之中
    //或者这DecorView就是这个根布局
    //布局Id:  public static final int ID_ANDROID_CONTENT = 
    //                                    com.android.internal.R.id.content;
    //在这里将window和decorView做关联
             installDecor();
     } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    ...
    //简而言之就是 mContentParent.addChildView
    //可以看 setContentView(View view)方法
     mLayoutInflater.inflate(layoutResID, mContentParent);
    

    仔细研究 installDecor()就会得出结论:


    image.png

    接下来就到了ViewGroup的addView方法中:

      // addViewInner() will call child.requestLayout() when setting the new LayoutParams
            // therefore, we call requestLayout() on ourselves before, so that the child's request
            // will be blocked at our level
            requestLayout();
    

    这里是requestLayout() View绘制的起始

        @CallSuper
        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()) {
                mParent.requestLayout();
            }
            if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
                mAttachInfo.mViewRequestingLayout = null;
            }
        }
    

    但是这时候只是Activit.onCreate()方法,在这里只是建立了结构关系在requestLayout()中是过不去的 (但是当view绑定了ViewRootImpl就能成功调用parent的requestLayout方法)
    不过在这里我有了一个结论,view自身并不进行requestLayout()或者说view不会自身主动调用onMeasure,onLayout,onDraw

    在哪里将View和ViewRoot建立联系呢?
    答案是Activity启动时,ActivityThread.handleResumeActivity()方法中建立了它们两者的关联关系,(在之前文章里我们说了 View真正显示时机是在ActivityThread的handleResumeActivity时)

     r.window = r.activity.getWindow();
                    View decor = r.window.getDecorView();
                    decor.setVisibility(View.INVISIBLE);
                    ViewManager wm = a.getWindowManager();
                    WindowManager.LayoutParams l = r.window.getAttributes();
                    a.mDecor = decor;
                    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                    l.softInputMode |= forwardBit;
                    if (r.mPreserveWindow) {
                        a.mWindowAdded = true;
                        r.mPreserveWindow = false;
                        // Normally the ViewRoot sets up callbacks with the Activity
                        // in addView->ViewRootImpl#setView. If we are instead reusing
                        // the decor view we have to notify the view root that the
                        // callbacks may have changed.
                        ViewRootImpl impl = decor.getViewRootImpl();
                        if (impl != null) {
                            impl.notifyChildRebuilt();
                        }
                    }
                    if (a.mVisibleFromClient && !a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);//重点
                    }
    

    wm 我们知道起始是WindowManager其实现类是 WindowManagerGlobal,让我们看看这个addview方法:

       root = new ViewRootImpl(view.getContext(), display);
    
       root.setView(view, wparams, panelParentView);
    

    原来 ViewRootImpl是在 WindowManager.addView()的时候创建的呀,创建后就调用了ViewRootImpl的方法啊:
    这里是setView方法,这个方法干了两件特别重要的事情

            //将DecorView和ViewRootImpl建立关系:
                 1.   view.assignParent(this);
                    // Schedule the first layout -before- adding to the window
                    // manager, to make sure we do the relayout before receiving
                    // any other events from the system.
                 2.   requestLayout();
    

    1.建立了什么样的关系呢?

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

    嘿嘿 原来RootViewImpl成了所有view的ParentView,这样后面关系图就解释的通了

    2.这里是requestLayout方法:

     @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();//这里用来检测是否是在主线程
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    //这个request方法来自于ViewParent这个接口(ViewGroup和ViewRootImpl都实现了这个接口)
    void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    //重点在这句话:
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    //mTraversalRunnable�这里执行了这个Runnable
              ...
            }
        }
    

    这里只有一句话 执行 doTraversal()

    final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    //几经流转 终于到了最终的控制方法:
    void doTraversal() {
        ...
                performTraversals();
        ...
        }
    

    performTraversals():

      // Ask host how big it wants to be
     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    

    再向下看 如果layoutParams中设置的Weight则需要根据weight计算出子view的宽高再次测量

      if (lp.horizontalWeight > 0.0f) {
                            width += (int) ((mWidth - width) * lp.horizontalWeight);
                            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                    MeasureSpec.EXACTLY);
                            measureAgain = true;
                        }
                        if (lp.verticalWeight > 0.0f) {
                            height += (int) ((mHeight - height) * lp.verticalWeight);
                            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                    MeasureSpec.EXACTLY);
                            measureAgain = true;
                        }
    
                        if (measureAgain) {
                            if (DEBUG_LAYOUT) Log.v(mTag,
                                    "And hey let's measure once more: width=" + width
                                    + " height=" + height);
                            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                        }
    

    在这里调用和ViewRootImpl绑定的View(DecorView)的绘制方法

     private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
            try {
                mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    关系如图所示:


    图片.png

    这时候我们必须了解下什么是DecorView了:

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks 
    

    当时我在View的measure方法中纠结了好久:

    因为view的measre方法只是记录自身的宽高却没有调用父容器的测量方法和调用子View的测量方法

    原来是考虑到不同布局测量方式都不太相同 具体实现都在其实现类里如FrameLayout的onMeasure()
    接下来是当View&ViewGroup内容改变的时候就会调用当前方法的requestLayout():调用父容器的requestLayout()直到调用到ViewRootImpl的requestLayout。

    相关文章

      网友评论

        本文标题:View&ViewGroup的绘制机制

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