紅橙Darren視訊筆記 view的繪製流程(上) onMeasure測量程式碼分析 基於API27

洌冰發表於2020-12-14

一.準備工作Activity的onCreate和onResume呼叫過程

從ActivityThread的handleLaunchActivity開始進行程式碼跟蹤

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ...

        Activity a = performLaunchActivity(r, customIntent);//一直往後跟 呼叫了Activity的onCreate

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);//一直往後跟 呼叫了Activity的Resume
        } 
    }

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
        ...
        return activity;
    }

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        ...
        r = performResumeActivity(token, clearHide, reason);

        ...
    }

    public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide, String reason) {
        ...
            r.activity.performResume();

        ...
        return r;
    }

Instrumentation

    public void callActivityOnCreate(Activity activity, Bundle icicle,
            PersistableBundle persistentState) {
        prePerformCreate(activity);
        activity.performCreate(icicle, persistentState);
        postPerformCreate(activity);
    }

    public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();//關鍵
        
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    am.match(activity, activity, activity.getIntent());
                }
            }
        }
    }

Activity

    @UnsupportedAppUsage
    final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        ...
        if (persistentState != null) {
            onCreate(icicle, persistentState);
        } else {
            onCreate(icicle);
        }
        ...
    }

    final void performResume(boolean followedByPause, String reason) {
        dispatchActivityPreResumed();
        performRestart(true /* start */, reason);

        ...
        // mResumed is set by the instrumentation
        mInstrumentation.callActivityOnResume(this);//關鍵
        ...
        dispatchActivityPostResumed();
    }

上面的程式碼是 為什麼view獲取寬高為0 一篇中activity的onCreate和onResume的呼叫過程

二.準備工作2 追蹤程式碼到performTraversals的呼叫

網上的很多文章說繪製流程裡面有一個關鍵方法 performTraversals 下面我們看接下來是如何呼叫到那裡的
我們從ActivityThread handleResumeActivity方法繼續跟蹤
ActivityThread

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);//上面我們曾經走到過這裡

        ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//1 1和2不確定呼叫的哪一個 但是他們的程式碼類似
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

        ...
            if (r.activity.mVisibleFromClient) {
                ViewManager wm = a.getWindowManager();
                View decor = r.window.getDecorView();
                wm.updateViewLayout(decor, l);//2 1和2不確定呼叫的哪一個 但是他們的程式碼類似
            }

        ...
    }

上面的程式碼中addView和updateViewLayout不確定呼叫的是那一個 但是他們的程式碼類似 我們以updateViewLayout為例 我們需要分析這段程式碼

if (r.activity.mVisibleFromClient) {
    ViewManager wm = a.getWindowManager();
    View decor = r.window.getDecorView();
    wm.updateViewLayout(decor, l);
}

updateViewLayout跟下去顯然是一個hook 要找到真正實現的型別 那麼目標是需要確定具體呼叫的是哪一個類的updateViewLayout
wm是a.getWindowManager()獲得的 我們看看Activity的對應方法

    /** Retrieve the window manager for showing custom windows. */
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

    @UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        ...
            mWindowManager = mWindow.getWindowManager();
        ...
    }

繼續跟到了Window的getWindowManager

    public WindowManager getWindowManager() {
        return mWindowManager;
    }
	public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

到此為止我們看到我們最開始想看的wm其實是WindowManagerImpl的例項
我們跟蹤WindowManagerImpl的updateViewLayout方法 看看有什麼收穫
WindowManagerImpl

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);//關鍵點
        }
    }

ViewRootImpl的方法

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            ...
            if (newView) {
                mSoftInputMode = attrs.softInputMode;
                requestLayout();//關鍵點
            }

            // Don't lose the mode we last auto-computed.
            if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                    == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
                        & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                        | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
            }

            mWindowAttributesChanged = true;
            scheduleTraversals();
        }
    }

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

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//看mTraversalRunnable這個Runnable
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();//看doTraversal
        }
    }

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

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

            performTraversals();//最終呼叫到了performTraversals 這一階段目標達成

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

最終確實呼叫到了performTraversals 這一階段目標達成

三.performTraversals的三個重要方法

我們接近正題了 需要分析具體的繪製流程 performTraversals的程式碼非常長 大概800行左右 我們只要關心重點的三個方法的呼叫

    private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;

       ...
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                            + " mHeight=" + mHeight
                            + " measuredHeight=" + host.getMeasuredHeight()
                            + " coveredInsetsChanged=" + contentInsetsChanged);

                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//關鍵方法1

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    ...
        } 
                ...
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);//關鍵方法2

            ...
        }

        ...

        if (!cancelDraw) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            performDraw();//關鍵方法3
        } else {
            if (isViewVisible) {
                // Try again
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
    }

下面有三個方法是重點
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//關鍵方法1
performLayout(lp, mWidth, mHeight);//關鍵方法2 之後的部落格再看
performDraw();//關鍵方法3 之後的部落格再看

四.以LinearLayout為例 分析測量方法measureVertical

為了方便分析 舉一個簡單的例子

<?xml version="1.0" encoding="utf-8"?><!-- step2 在xml中使用自定義屬性 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/outer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/inner"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text1" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text2" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text3" />

    </LinearLayout>

</LinearLayout>

假設這個xml在setContentView被呼叫 以此為前提進行分析 繼續追蹤performMeasure
performMeasure–>mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);–>onMeasure(widthMeasureSpec, heightMeasureSpec);//以當前例子說的話 呼叫的是LinearLayout的對應方法
LinearLayout–>onMeasure方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);//看這個
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

	void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {//需要仔細看看
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;

        final int count = getVirtualChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;

        int nonSkippedChildCount = 0;

        // See how tall everyone is. Also remember max width.
        //迴圈測量 記錄每一個子view的高度 以及寬度最大的一個
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ...

            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;//當前子view高度為0 weight不為0 說明使用了權重
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // 不必費心測量那些只使用剩餘空間的子view。如果我們有剩餘空間,將會分配給這些子view。
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    // 父佈局heightMode不是EXACTLY的case 子view又使用權重 那麼子view的高度為WRAP_CONTENT
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    lp.height = LinearLayout.LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                // 確定這個子view需要多大空間。如果這個或之前的子view有使用權重的,那麼我們允許它使用剩餘的空間(如果需要,我們將在以後壓縮佈局大小)
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);//關鍵點 之後分析

                final int childHeight = child.getMeasuredHeight();//得到了子view的高度!
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                        lp.bottomMargin + getNextLocationOffset(child));
                //mTotalLength是子view的高度 但是名字看起來更像寬度?
                //重點 高度累加!!!!!!!!!!!!!!!!!!!!!!

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
                mBaselineChildTop = mTotalLength;
            }

            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LinearLayout.LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);//記錄目前為止最大的寬度!!!!!!!!!
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LinearLayout.LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            //因為useLargestChild預設為false 使用的case比較少 可以先忽略這個分支
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }
        //上面是子view的測量環節
        //下面是父容器的測量環節

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        // Either expand children with weight to take up available space or
        // shrink them if they extend beyond our current bounds. If we skipped
        // measurement on any children, we need to measure them now.
        //如果子view的權重超過了現有的範圍,那麼他們就可以擴大他們的權重。如果我們跳過了對任何子view的測量,我們現在就需要測量他們。//可以先不看
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight;
                if (childWeight > 0) {
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LinearLayout.LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LinearLayout.LayoutParams.MATCH_PARENT;

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    weightedMaxWidth);


            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {//可以先不看
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);//這句話呼叫後才會有寬高!!!!!!!!!!!!!!!!!!!!!!

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

仔細閱讀measureVertical方法 我們可以知道該方法的大致流程是先測量所有子view的寬高 寬度記錄最大值 高度進行累加。子view測量完畢之後 開始為父容器測量寬高 基本上就是父佈局的寬度取自最寬的子view 高度是子view的累加
注意child.measure(childWidthMeasureSpec, childHeightMeasureSpec);會呼叫到onMeasure方法 因此我們自定義view的時候 onMeasure的引數就是來自這裡

五.子view的測量

繼續追蹤 LinearLayout的measureChildBeforeLayout 看看是如何測量子view的

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);//繼續跟
    }

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);//這裡需要關注
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);//這裡需要關注

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//輪迴
        //當調到textview的時候會呼叫到view的measure
        //當調到LinearLayout的時候會呼叫到LinearLayout的measure
    }

題外話 關於測量模式

我之前的部落格
紅橙Darren Android視訊筆記 自定義view的三個構造方法以及一種實現換膚的方案 測量mode
提到
MeasureSpec.AT_MOST;//wrap_content
MeasureSpec.EXACTLY;//match_parent fill_parent 指定數值
MeasureSpec.UNSPECIFIED;//不指定大小
但是這種說法嚴格意義是不對的
因為子佈局的大小其實是由父佈局和子佈局一起決定的 至於我為什麼這麼說 請繼續往下看

getChildMeasureSpec呼叫的是ViewGroup的對應方法 我們跟進去看一看

    //注意這裡的引數 spec是父容器的spec padding是父容器的padding+margin childDimension是子佈局的指定值(寬或者高)
	public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //specMode specSize是呼叫者的測量模式和size 即父容器的測量模式
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);//代表父佈局的實際大小

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        //父佈局有一個確切的大小
        case MeasureSpec.EXACTLY:
                //ViewGroup.LayoutParams.MATCH_PARENT = -1;
                //ViewGroup.LayoutParams.WRAP_CONTENT = -2;
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //父佈局有一個確切的大小 如果子佈局指定寬高 子佈局指定的值就是實際的值 子佈局測量模式為EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;          
                resultMode = MeasureSpec.EXACTLY;
                //父佈局有一個確切的大小 如果子佈局指定MATCH_PARENT 子佈局的size父佈局一樣大 子佈局測量模式為EXACTLY
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;          
                resultMode = MeasureSpec.AT_MOST;
                //父佈局有一個確切的大小 如果子佈局指定WRAP_CONTENT 子佈局的size父佈局一樣大 子佈局測量模式為AT_MOST(不能超過父佈局)
            }
            break;

        // Parent has imposed a maximum size on us
        //父佈局是wrap_content 即父佈局沒有確切的大小 給了子佈局最大尺寸
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //父佈局是wrap_content 如果子佈局指定尺寸 那麼子佈局的size就是指定尺寸 子佈局測量模式為EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
                //父佈局是wrap_content 如果子佈局指定MATCH_PARENT 那麼子佈局的size就是父佈局的size 子佈局測量模式為AT_MOST
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
                //父佈局是wrap_content 如果子佈局指定WRAP_CONTENT 那麼子佈局的size就是父佈局的size 子佈局測量模式為AT_MOST
                //為什麼不合並case呢。。。 是為了邏輯更清晰麼?
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //父佈局沒有指定size知道期望子佈局size 如果子佈局指定大小 那麼子佈局的size就是指定的值 子佈局測量模式為EXACTLY
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

我們可以看到上面的switch case根據父容器的EXACTLY AT_MOST UNSPECIFIED 以及子佈局的指定大小 MATCH_PARENT WRAP_CONTENT 一共3*3=9種case 給出了子佈局的最終測量大小 因此我說單純看子佈局的測量模式沒有作用
其中有個比較特別的例子
父佈局是wrap_content 如果子佈局指定MATCH_PARENT 那麼子佈局的size就是父佈局的size 子佈局測量模式為AT_MOST
也就是子佈局還是wrap content的

<LinearLayout
    android:id="@+id/inner"
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="text1" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text2" />

</LinearLayout>

就像上面這個例子 也就是上面這種情況 text1不會佔滿整個父容器

六.父容器的測量

我們回退到LinearLayout的measureVertical的下半部分看一下父佈局的測量
與子view的測量來自getChildMeasureSpec 不同 父佈局的寬高的測量結果來自
view的resolveSizeAndState方法

    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {//size是期望值
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);//specSize是測量值
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

七.小結

整個測量過程 由外至內傳遞MeasureSpec 有了父佈局的MeasureSpec再計運算元view 然後一級級由內至外執行measure方法

相關文章