Java&Android日更补完计划Android开发经验谈Android开发

View&ViewGroup的绘制机制

2017-12-21  本文已影响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。

上一篇下一篇

猜你喜欢

热点阅读