Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> LinearLayout源碼解析

LinearLayout源碼解析

編輯:關於Android編程

為什麼學習

自從學了Android自定義控件的一些知識,總是處於似懂非懂狀態,說都說了上來,自己在項目裡封裝了一些自定義控件,但是還是缺乏一個很直觀的了解。所以去了解學習下Android是如何封裝控件的,就從簡單的入手,分析下LinearLayout是如何實現的

什麼是LinearLayout

作為最基礎的布局,所以從事過Android開發的同學都應該非常了解
中文解釋應該叫做線性布局,相比如RelativeLayout,LinearLayout更簡單,在沒有weight的情況也每次只要測量一次就夠,而RelativeLayout每次都需要測量兩次

一些LinearLayout需要注意的屬性

orientation 縱向排布或者水平排布

weight 權重,用於分配LinearLayout剩下的空間(會詳細介紹)

measureWithLargestChild 這個屬性不常見,如果賦值為true的話,所有
weight子View都會采用最大View的最小尺寸(為什麼Android要設計這個屬性,我也不是很理解)

源碼分析

一般所有控件類的源碼,都會從 measure, layout和draw3個方法入手,查看他們的回調函數onMeasure, onLayout和onDraw
只要明白這3個流程,一般控件的整個實現也就明白了
LinearLayout作為一個ViewGroup的子類,主要作為一個布局容器出現,所以我們需要重點查看寫onMeasure方法

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

從上面代碼看到,LinearLayout的onMeasure方法實現非常簡潔,根據布局方向分為measureVertical和measureHorizontal。下後面的onLayout和onDraw也是如此。鑒於內部實現基本一模一樣,我在這只分析縱向的實現

/**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
    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;

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

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

            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // Optimization: don't bother measuring children who are going to use
                // leftover space. These views will get measured again down below if
                // there is any leftover space.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;

                if (lp.height == 0 && lp.weight > 0) {
                    // heightMode is either UNSPECIFIED or AT_MOST, and this
                    // child wanted to stretch to fill available space.
                    // Translate that to WRAP_CONTENT so that it does not end up
                    // with a height of 0
                    oldHeight = 0;
                    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).
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }

                final int childHeight = child.getMeasuredHeight();
                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 delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);

                if (child.getVisibility() == View.GONE) {
                    continue;
                }

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

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    // Child said it could absorb extra space -- give him his share
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;

                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);

                    // TODO: Use a field like lp.isMeasured to figure out if this
                    // child has been previously measured
                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        // child was measured once already above...
                        // base new measurement on stored values
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }

                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        // child was skipped in the loop above.
                        // Measure for this first time here      
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }

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

整個方法代碼不長,就300行左右,但是思路十分清晰 我們根據流程,一步一步來看

首先是初始化了一堆變量
我們挑幾個重要的看

//記錄內部使用的高度,別被字面意思誤導了以為是LinearLayout的高度
 mTotalLength = 0;
 //權重值的總和
 float totalWeight = 0;
 //子view的數量,
 final int count = getVirtualChildCount();
 //其實調用的都是getChildCount(),外面套一層getVirtualChildCount()   
 //可能是為了讓讀者更好的理解
 int getVirtualChildCount() {
        return getChildCount();
    }
 //LinearLayout的高度模式和寬度模式
 //如果這部分知識不理解的需要去看下Measure的過程      
 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 final int heightMode=MeasureSpec.getMode(heightMeasureSpec);

初始化變量之後,就開始遍歷所有子View了,然後對子View進行測量

             //首先把子View取出來
            final View child = getVirtualChildAt(i);
            //如果子View是null就繼續測量下一個子View
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }
            //如果子View是GONE的也不算在總高度裡面,這裡也能看出GONE和INVISIBLE的區別
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
            //如果有分割線,就把分割線高度加上
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

然後


//有時候我們在代碼裡面通過Inflater服務,動態加載一個布局,然後去設置他的LayoutParams,如果不引用父容器的LayoutParams就會報一個強轉錯誤,原因就在這個 父容器在add,measure的時候都會把子View的LayoutParams強轉成自己的類型
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

//計入總權重
totalWeight += lp.weight;

//這裡就值得注意下了如果當前的LinearLayout是EXACTLY模式,且子view的高度為0,且權重大於0
//這個子view只有在LinearLayout高度有剩余的時候,才會根據權重的占比去平分剩余空間
//上文說的二次測量也就指的這部分
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
    // Optimization: don't bother measuring children who are going to use
    // leftover space. These views will get measured again down below if
    // there is any leftover space.
    final int totalLength = mTotalLength;
    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
    skippedMeasure = true;
    }

//如果不是上述的情況
else {
    int oldHeight = Integer.MIN_VALUE;

        if (lp.height == 0 && lp.weight > 0) {
     // heightMode is either UNSPECIFIED or AT_MOST, and this
     // child wanted to stretch to fill available space.
     // Translate that to WRAP_CONTENT so that it does not end up
     // with a height of 0
     //這裡其實官方的注釋講了也挺清楚的,到了這步,當前的LinearLayout的模式
     //肯定是UNSPECIFIED或者MOST,因為EXACTLY模式會進入上一個判斷
     //然後把子View的高度賦值成-1(WRAP_CONTENT)
        oldHeight = 0;
        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).
  //這裡就開始測算子View了
  //如果當前的LinearLayout不是EXACTLY模式,且子View的weight大於0,優先會把當前LinearLayout的全部可用高度用於子View測量
        measureChildBeforeLayout(
          child, i, widthMeasureSpec, 0, heightMeasureSpec,
          totalWeight == 0 ? mTotalLength : 0);
// 重置子控件高度,然後進行精確賦值
        if (oldHeight != Integer.MIN_VALUE) {
            lp.height = oldHeight;
        }

       final int childHeight = child.getMeasuredHeight();
       final int totalLength = mTotalLength;
       //加上子View的margin值
       mTotalLength = Math.max(totalLength, totalLength+childHeight + lp.topMargin +lp.bottomMargin + getNextLocationOffset(child));

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

//totalWeight == 0 ? mTotalLength : 0
//這裡的totalHeight就是有這個決定的
//如果為0,就會把所有可用高度給子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();
    //這個就和普通的ViewGroup實現方法一樣了,根據子View的LayoutParams和父容器的MeasureSpec共同決定了子View的大小
    //getChildMeasureSpec是屬於ViewGroup的方法
    final int childWidthMeasureSpec = getChildMeasureSpec(
        parentWidthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + 

    final int childHeightMeasureSpec getChildMeasureSpec(
        parentHeightMeasureSpec,
        mPaddingTop + mPaddingBottom + lp.topMargin
        + lp.bottomMargin + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


//在這兩段代碼之間還有些雜七雜八的處理,如果讀者有興趣可以自己閱讀分析下
//當測量完子View的大小後,總高度會再加上padding的高度
// Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        //如果設置了minimumheight屬性,會根據當前使用高度和最小高度進行比較
        //然後取兩者中大的值
        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.
        //到了這裡,會再對帶weight屬性的子View進行一次測繪
        //首先計算屬於高度
        int delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
        //如果設置了weightSum就會使用你設置的weightSum,否則采用當前所有子View的權重和。所以如果要手動設置weightSum的時候,千萬別計算錯誤哦
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;
        //這裡的代碼就和第一次測量很像了
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);

                if (child.getVisibility() == View.GONE) {
                    continue;
                }

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

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    // Child said it could absorb extra space -- give him his share
                    //子控件的weight占比*剩余高度
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    //剩余高度減去分配出去的高度
                    delta -= share;

                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);

                    // TODO: Use a field like lp.isMeasured to figure out if this
                    // child has been previously measured
                    //如果是當前LinearLayout的模式是EXACTLY
                    //那麼這個子View是沒有被測量過的,就需要測量一次
                    //如果不是EXACTLY的,在第一次循環裡就被測量一些了
                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        // child was measured once already above...
                        // base new measurement on stored values
                        //如果是非EXACTLY模式下的子View就再加上
                        //weight分配占比*剩余高度
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }
                       //重新測量一次,因為高度發生了變化
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        // child was skipped in the loop above.
                        // Measure for this first time here      
                        //如果是EXACTLY模式下的
                        //這裡只會把weight占比所擁有的高度分配給你的子View
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }

                    // 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?
        }

上述就是onMeasure的主要分析
注意點
1.會根據當前LinearLayout的模式分成2條支線,如果是EXACTLY模式下的weight不為0,且高度設置為0的子View優先級是最低的。如果LinearLayout剩余空間不足,就會不顯示
但是如果是AT_MOST的weight不為0,且高度設置為0就會優先獲得高度
2.為LinearLayout動態添加子View的時候,子View的LayoutParams一定要是LinearLayout的內部類(適用於其他ViewGroup子類)

舉個例子

所有的源碼解析都是我們自己根據代碼的推論,配合幾個demo跑下會理解了更深



    

    


在分析之前大伙兒先預測下結果,思索下再往下看

假設我們的屏幕是1000dp的高度

父容器LinearLayout是EXACTLY模式,但是TextView1本身的height是300dp,所以會進入第一次測量
TextView1 在第一次測算時拿到了300dp的高度

然後TextView因為是match,所以會拿到1000dp的高度

然後由於有weight的子view,所以進入第二次測量

int delta = heightSize - mTotalLength;

delta = 1000 - 1300; // 結果是-300

int share = (int) (childExtra * delta / weightSum);

share = 2.0 * -300 / 2.0 //share的結果也是-300

MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTL);
所以最後TextView1的高度是0.不會顯示在屏幕上,屏幕上應該被Textview2充滿。
不信的話,大家可以試一下

好了LinearLayout的主要測量就講到這了

onLayout()和onDraw()相對來說就比較簡單,大家自己看下就好了

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved