UI繪制流程

一、從setContentView(R.layout.activity_main);入手了解UI的繪制起始過程

1.Activity.java

public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);//①

 initWindowDecorActionBar();

}

PhoneWindow中的窗體類型,自定義窗體可以用到這些類型

WindowManager 中窗體的類型:

* @see #TYPE_BASE_APPLICATION

* @see #TYPE_APPLICATION

* @see #TYPE_APPLICATION_STARTING

* @see #TYPE_DRAWN_APPLICATION

* @see #TYPE_APPLICATION_PANEL

* @see #TYPE_APPLICATION_MEDIA

* @see #TYPE_APPLICATION_SUB_PANEL

* @see #TYPE_APPLICATION_ABOVE_SUB_PANEL

* @see #TYPE_APPLICATION_ATTACHED_DIALOG

* @see #TYPE_STATUS_BAR

* @see #TYPE_SEARCH_BAR

* @see #TYPE_PHONE

* @see #TYPE_SYSTEM_ALERT

* @see #TYPE_TOAST

* @see #TYPE_SYSTEM_OVERLAY

* @see #TYPE_PRIORITY_PHONE

* @see #TYPE_STATUS_BAR_PANEL

* @see #TYPE_SYSTEM_DIALOG

* @see #TYPE_KEYGUARD_DIALOG

* @see #TYPE_SYSTEM_ERROR

* @see #TYPE_INPUT_METHOD //輸入法

* @see #TYPE_INPUT_METHOD_DIALOG

*/

2.getWindow()拿到的是Window的實(shí)現(xiàn)類PhoneWindow

mWindow = new PhoneWindow(this, window);

PhoneWindow源碼:

在 com.android.internal.policy包下面

@Override

public void setContentView(int layoutResID) {

// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
      installDecor();//②
}

……

mLayoutInflater.inflate(layoutResID,mContentParent//⑥最后
    將布局渲染到幀布局當(dāng)中。
}
private void installDecor() {

if (mDecor == null) {

    mDecor = generateDecor();//③生成一個(gè)DecorView(繼承的FrameLayout)
  mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  mDecor.setIsRootNamespace(true);

if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
      mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
  }
}
if (mContentParent == null) {
      mContentParent = generateLayout(mDecor);//④
}
    protected ViewGroup generateLayout(DecorView decor) {//⑤
    
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
              layoutResource = R.layout.screen_swipe_dismiss;
      } 
.............
      上面判斷加載哪一種布局,有的有actionBar 有的沒有,還有其他種類的布局,然后加載添加到decor中。
下圖中就是一種簡單的布局。不含有ActionBar,但是有一個(gè)ViewStub方便以后添加標(biāo)題欄

View in = mLayoutInflater.inflate(layoutResource, null);

decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

mContentRoot = (ViewGroup) in;

//然后將我們的布局填充到這個(gè)布局當(dāng)中
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
    }
}
1_Activity加載UI-類圖關(guān)系和視圖結(jié)構(gòu).png

D:\Software\Android\SDK\platforms\android-23\data\res ,查看系統(tǒng)的資源文件

這里我們來看一下snackbar添加到布局中的原理

 private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        do {
            if (view instanceof CoordinatorLayout) {
                // We've found a CoordinatorLayout, use it
                return (ViewGroup) view;
            } else if (view instanceof FrameLayout) {
                if (view.getId() == android.R.id.content) {
                     //見上圖中的id/content 將SnackBar添加到DecorView中FrameLayout
                    // If we've hit the decor content view, then we didn't find a CoL in the
                    // hierarchy, so use it.
                    return (ViewGroup) view;
                } else {
                    // It's not the content view but we'll use it as our fallback
                    fallback = (ViewGroup) view;
                }
            }

            if (view != null) {
                // Else, we will loop and crawl up the view hierarchy and try to find a parent
                final ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);

        // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
        return fallback;
    }

二、measure、layout、draw的三個(gè)執(zhí)行流程

View.java類

measure:測(cè)量,測(cè)量自己有多大,如果是ViewGroup的話會(huì)同時(shí)測(cè)量里面的子控件的大小

layout:擺放里面的子控件bounds(left,top,right,bottom)

draw:繪制 (直接繼承了view一般都會(huì)重寫onDraw)

ViewGroup.java

看View.java類的源碼:

1.view的requestLayout()方法開始,遞歸地不斷往上找父容器,最終找到DecorView

@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();//不斷的遞歸往上查找,一直到找到DecorView
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

2.執(zhí)行了DecorView的ViewRootImp類的performTranversal()方法 (ViewRootImp類:是PhoneWindow和DecorView的橋梁)

3.performTranversal里面會(huì)調(diào)用以下三個(gè)方法

performTranversal(){
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
performDraw();
}
performTraversals方法控制View繪制流程圖.jpg

4.performMeasure會(huì)執(zhí)行View的measure方法,從這里開始測(cè)量View

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

5.View的measure方法里面會(huì)調(diào)用onMeasure方法

  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
        ............
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
            resolveRtlPropertiesIfNeeded();
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
        }   
      .........
    }

onMeasure根據(jù)傳入父類的MeasureSpec,得到自身的大小。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

子類的MeasureSpec由父類和LayoutParams一起決定。


getChildMeasureSpec方法分析.png

下面我們?cè)賮砜纯磄etDefaultSize這個(gè)方法:

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

結(jié)合上圖可以知道如何測(cè)量出View的大小。
我們?cè)賮砜纯矗篻etSuggestedMinimumWidth()方法。

  protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
  public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }
   public int getIntrinsicWidth() {
        return -1;
    }

這種情況是確保View設(shè)置了背景也能準(zhǔn)確的測(cè)量出View的大小。

三 ViewGroup的測(cè)量流程

View樹的源碼measure流程圖.png

在View的測(cè)量流程中,通過makeMeasureSpec來保存寬高信息,在其他流程通過getMode或getSize得到模式和寬高。那么問題來了,上面提到MeasureSpec是LayoutParams和父容器的模式所共同影響的,那么,對(duì)于DecorView來說,它已經(jīng)是頂層view了,沒有父容器,那么它的MeasureSpec怎么來的呢?
閱讀源碼的時(shí)候一定要帶著疑問,這時(shí)候我們提出了問題,那么接下來就要在源碼中找出答案。
MeasureSpec肯定是在測(cè)量之前就已經(jīng)準(zhǔn)備好,這時(shí)候我們就想什么時(shí)候開始測(cè)量的呢?上面已經(jīng)提到過ViewRootImpl#PerformTraveals這個(gè)方法是UI繪制的起點(diǎn),那我們就去那里看一下。果不其然,看到了我們想看的東西。

private void performTraversals() {
            ...

        if (!mStopped) {
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  // 1
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);       
            }
        } 
·······
}

/**
 * @param windowSize
 *            The available width or height of the window
 *
 * @param rootDimension
 *            The layout params for one dimension (width or height) of the
 *            window.
 *
 * @return The measure spec to use to measure the root view.
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    //省略...

    }
    return measureSpec;
}

getRootMeasureSpec(desiredWindowWidth,lp.width)方法,其中desiredWindowWidth就是屏幕的尺寸,并把返回結(jié)果賦值給childWidthMeasureSpec成員變量(childHeightMeasureSpec同理),因此childWidthMeasureSpec(childHeightMeasureSpec)應(yīng)該保存了DecorView的MeasureSpec。

下面我們來看一下ViewGroup的測(cè)量過程,ViewGroup是繼承了View,調(diào)用的是View的measure方法,我們主要看一下ViewGroup的onMeasure方法,就以LinearLayout為例:

    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;

        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

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

            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

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

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // 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) {
                    // 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 = 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).
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                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));

                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 == 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 == 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 (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            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));
            }
        }

        // 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.
        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 LayoutParams lp = (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 == LayoutParams.MATCH_PARENT;

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

                allFillParent = allFillParent && lp.width == 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);
        }
    }

這個(gè)源碼是比較復(fù)雜的,但是我們可以看出的是,如果自身大小是確定的,那么高度就是自身的大小,否則的話需要測(cè)量所有的子View,同時(shí)還有分割線之類的,將他們的高度疊加。設(shè)置自身的大小。

總結(jié)

一、measure的過程

如何去合理的測(cè)量一顆View樹?

如果ViewGroup和View都是直接指定的寬高,我還要測(cè)量嗎?

正是因?yàn)楣雀柙O(shè)計(jì)的自適應(yīng)尺寸機(jī)制(比如Match_parent,wrap_content),造成了寬高不確定,所以就需要進(jìn)程測(cè)量measure過程。

measure過程會(huì)遍歷整顆View樹,然后依次測(cè)量每一個(gè)View的真實(shí)的尺寸。(樹的遍歷--先序遍歷)

MeasureSpec:測(cè)量規(guī)格

int 32位:010111100011100

拿前面兩位當(dāng)做mode,后面30位當(dāng)做值。

1.mode:

1) EXACTLY: 精確的。比如給了一個(gè)確定的值 100dp

  1. AT_MOST: 根據(jù)父容器當(dāng)前的大小,結(jié)合你指定的尺寸參考值來考慮你應(yīng)該是多大尺寸,需要計(jì)算(Match_parent,wrap_content就是屬于這種)

  2. UNSPECIFIED: 最多的意思。根據(jù)當(dāng)前的情況,結(jié)合你制定的尺寸參考值來考慮,在不超過父容器給你限定的只存的前提下,來測(cè)量你的一個(gè)恰好的內(nèi)容尺寸。

用的比較少,一般見于ScrollView,ListView(大小不確定,同時(shí)大小還是變的。會(huì)通過多次測(cè)量才能真正決定好寬高。)

2.value:寬高的值。

經(jīng)過大量測(cè)量以后,最終確定了自己的寬高,需要調(diào)用:setMeasuredDimension(w,h)

寫自定義控件的時(shí)候,我們要去獲得自己的寬高來進(jìn)行一些計(jì)算,必須先經(jīng)過measure,才能獲得到寬高---不是getWidth(),而是getMeasuredWidth()

也就是當(dāng)我們重寫onMeasure的時(shí)候,我們需要在里面調(diào)用child.measure()才能獲取child的寬高。

從規(guī)格當(dāng)中獲取mode和value:

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

反過來將mode和value合成一個(gè)規(guī)格呢:

MeasureSpec.makeMeasureSpec(resultSize, resultMode);

ViewGroup:

設(shè)計(jì)它的目的是什么?

1)作為容器處理焦點(diǎn)問題。

2)作為容器處理事件分發(fā)問題;

3)控制容器添加View的流程:addView(),removeView()

4)抽出了一些容器的公共的工具方法:measureChildren,measureChild,measureChildWidthMargins方法。

-------------------重點(diǎn):-----------------------

玩自定義控件的時(shí)候,需要進(jìn)行測(cè)量measure,如何做好這件事?

兩種情況:

1.繼承自View的子類

只需要重寫onMeasure測(cè)量好自己的寬高就可以了。

最終調(diào)用setMeasuredDimension()保存好自己的測(cè)量寬高。

套路:

    int mode = MeasureSpec.getMode(widthMeasureSpec);
    
    int Size = MeasureSpec.getSize(widthMeasureSpec);
    
    int viewSize = 0;
    
    switch(mode){
            case MeasureSpec.EXACTLY:
            viewSize = size;//當(dāng)前view的尺寸就為父容器的尺寸
            break;
        case MeasureSpec.AT_MOST:
            viewSize = Math.min(size, getContentSize());//當(dāng)前view的尺寸就為內(nèi)容尺寸和費(fèi)容器尺寸當(dāng)中的最小值。
            break;
        
        case MeasureSpec.UNSPECIFIED:
            viewSize = getContentSize();//內(nèi)容有多大,久設(shè)置多大尺寸。
            break;
        default:
            break;
    }
    
    //setMeasuredDimension(width, height);
    
    setMeasuredDimension(size);

2.繼承自ViewGroup的子類:

不但需要重寫onMeasure測(cè)量自己,還要測(cè)量子控件的規(guī)格大小。

可以直接使用ViewGroup的工具方法來測(cè)量里面的子控件,也可以自己來實(shí)現(xiàn)這一套子控件的測(cè)量(比如:RelativeLayout)

套路:

//1.測(cè)量自己的尺寸

ViewGroup.onMeasure();

//1.1 為每一個(gè)child計(jì)算測(cè)量規(guī)格信息(MeasureSpec)

ViewGroup.getChildMeasureSpec();

//1.2 將上面測(cè)量后的結(jié)果,傳給每一個(gè)子View,子view測(cè)量自己的尺寸

child.measure();

//1.3 子View測(cè)量完,ViewGroup就可以拿到這個(gè)子View的測(cè)量后的尺寸了

child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()

//1.4ViewGroup自己就可以根據(jù)自身的情況(Padding等等),來計(jì)算自己的尺寸

ViewGroup.calculateSelfSize();

//2.保存自己的尺寸

ViewGroup.setMeasuredDimension(size);


二、layout的過程
ViewGroup才有擺放的過程,有興趣的同學(xué)可以看一下,繼承自ViewGroup控件的源碼。
三、draw的過程

ViewGroup的onDraw方法默認(rèn)是不會(huì)調(diào)用的,因?yàn)樵赩iewGroup構(gòu)造方法里面就默認(rèn)設(shè)置了

setFlags(WILL_NOT_DRAW, DRAW_MASK);//原因是因?yàn)閂iewGroup本來就沒東西顯示,除了設(shè)置了背景,這樣就是為了效率。

如果需要它執(zhí)行onDraw可以,設(shè)置背景或者如下:

setWillNotDraw(false);

2.自繪控件View/ViewGroup

主要重寫onDraw方法繪制,還會(huì)要處理onMeasure,onLayout

3.組合控件。

比如封裝通用的標(biāo)題欄控件。

并不需要自己去繪制視圖上面顯示的內(nèi)容,而只是用系統(tǒng)原生的控件就可以了。

但是我們可以將幾個(gè)原生控件組合到一起,可以創(chuàng)建出的控件就是組合控件。

比如:在構(gòu)造方法里面LayoutInflater.from(context).inflate(R.layout.title,this);然后再加上業(yè)務(wù)邏輯。

最后的話:有些地方還有點(diǎn)粗糙,后面會(huì)繼續(xù)補(bǔ)充。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容