android复习之五大布局

一、Android五大布局

      有LinearLayout, RelativeLayout, FrameLayout, TableLayout, AbsoluteLayout


二、源码

      从源码入手,看各个布局在View体系中的关系,为何是view?因为android中的所有组件、所有layout都是继承view。

      


三、LinearLayout线性布局

      按照以往的开发经历,总结linearlayout的几点:


      3.1 XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

</LinearLayout>

 
      LinearLayout的orientation属性决定了该线性布局的子view是水平排列(horizontal)还是垂直排列(vertical)。
 
 
 
      有几个属性也很重要,baselineAligned基准线对齐的属性,默认为true,当baselineAligned="false"时,布局文件和他的子view的基准线将不会对齐。
 
 
 
      driver分割线属性,一般drivider="@color/ | @drawable/ | @android:"
      通常还有showDividers分割线位置属性,showDividers="none | middle | beginning | end"
 
 
 
      gravity内部子view的位置属性,通常有centre,left,right等。

      3.2 Source Code      

      下面简析LinearLayout的源码


      一般view的绘制过程主要有3个:onMeasure(), onLayout(), onDraw(),LinearLayout也是一样,下面依次分析。


      首先是onMeasure(),源码如下,非常简单,测量LinearLayout的宽度和高度,来决定这个布局的大小

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

      然后是onLayout(),用来确定放置该布局在屏幕的位置

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }


      最后是onDraw(),最终绘制这个布局
   @Override
    protected void onDraw(Canvas canvas) {
        if (mDivider == null) {
            return;
        }

        if (mOrientation == VERTICAL) {
            drawDividersVertical(canvas);
        } else {
            drawDividersHorizontal(canvas);
        }
    }

      发现以上3个绘制流程的实现方法惊人的相似,而且LinearLayout有个orientation的属性非常重要,它的值有vertical代表纵向排列和horizontal代表横向排列,直接决定了linear layout的排列方向。


      我们取onMeasure(int, int)的measureVertical(int, int)的源码分析,相关的解释注明在代码里了

    /**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec 被父View限制的水平空间需求
     * @param heightMeasureSpec 被父View限制的垂直空间需求
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;//内部子View的高度总和 //记得老师强调过这是子View的总高度,不是LinearLayout的高度
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;//权重值的总和

        final int count = getVirtualChildCount();//得到内部子View的数量

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);//LinearLayout的宽度模式
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);//LinearLayout的高度模式

        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.获取每个子View的高度,并记录最大宽度
        for (int i = 0; i < count; ++i) {//遍历内部的每个子View
            final View child = getVirtualChildAt(i);//得到子View的特殊标示,类似的有java.lang.StringBuilder的charAt(int)

            if (child == null) {//如果该view为空
                mTotalLength += measureNullChild(i);//measureNullChild(int)是当出现null child时候返回0
                continue;
            }

            if (child.getVisibility() == View.GONE) {//如果该View被隐藏掉
                i += getChildrenSkipCount(child, i);//getChildrenSkipCount是在测量或布局某view后返回要skip的children的数量,返回0
                continue;
            }

            if (hasDividerBeforeChildAt(i)) {//如果有分割线,就会把分割线的高度加上
                mTotalLength += mDividerHeight;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();//将子view的LayoutParams强制转换成LinearLlayout的LayoutParams类型


            totalWeight += lp.weight;//将子view的权重加到权重总值里

            // 当LinearLayout的高度模式是MeasureSpec.EXACTLY,并且子View的高度是0,权重>0
            // 这里说下MeasureSpec.EXACTLY,特殊测量模式之一,父view已经给子view决定好了一个精确的尺寸

            // 其他两个特殊测量模式是MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED
            // MeasureSpec.AT_MOST是view的尺寸不能超过一个特定值
            // MeasureSpec.UNSPECIFIED是可以随意决定view的尺寸
            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;

                // 当LinearLayout的高度模式是MeasureSpec.UNSPECIFIED或MeasureSpec.AT_MOST,并且子view想要伸缩来填充可使用空间的时候
                // 将子view的高度设置成wrap_content,所以最后高度不会以0结束
                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).
                // 此处翻译官方文档

                // 决定该子view想要多大。如果该子view或前一个view已经设置过weight,那么我们会允许它去使用所有可用空间(如果有需要我们会拉伸它)
                // 就是说把所有可用的高度都给这个view
                measureChildBeforeLayout(
                        child, i, widthMeasureSpec, 0, heightMeasureSpec,
                        totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {//重置子view的高度
                    lp.height = oldHeight;
                }

                final int childHeight = child.getMeasuredHeight();
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + //此处是比较子view测量前后的高度,取较大值
                        lp.bottomMargin + getNextLocationOffset(child));//getNextLocationOffset()返回0

                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}.
             * 此处继续翻译,加上自己的理解
             * 如果可应用的话,会估算额外的偏移量到子view的基准线
             * 后面当请求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.
            // 如果我们尝试用一个子view的标示给基线,
            // 快速故障来帮助开发人员,java集合也有一个fail-fast的错误检测机制

            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;
            // 当父view不是精确值或match_parent,而且子view的宽度是match_parent的时候,matchWidthLocally会变成true
            // 这个子view会占据父view水平方向上的所有空间
            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;//子view像素的左右边缘
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);//对比,获取加上边缘距离前后的最大值
            childState = combineMeasuredStates(childState, child.getMeasuredState());//combineMeasuredStates()会合并该view与上一个子view的测量状态


            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.
                 * 如果我们停止重新测量的话,权衡后的view宽度就没用了,所以要维持它们各自分开
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }//遍历获取每个子view的高度和最大高度的循环结束

        // 下面一段
        // 如果LinearLayout的高度模式是AT_MOST或UNSPECIFIED,将对weight的二次测量,来确定子view的大小
        // 如果是MeasureSpec.EXACTLY已经确定了高度的,就不需要二次测量
        // 为何要测量两次,因为布局除了子view还有自己的background,需要比较得到子view的总高度和background高度的较大值
        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        //如果设置了useLargestChild属性,且LinearLayout的高度模式是AT_MOST或UNSPECIFIED,则会重新测量总高度
        if (useLargestChild && //useLargestChild属性会使所有带weight属性的子视图具有最大子视图的最小尺寸
                (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
                // 解释negative的外边距,什么鬼??这个negative暂时不知怎么解释?负边距?
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + //此处仍是比较测量前后的高度,取较大值
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); //前面说过getNextLocationOffset()返回0
            }
        }

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

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); //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);
        }
    }


四、RelativeLayout相对布局

      4.1  XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/review_relativelayout">

</RelativeLayout>

      相对布局的xml属性,主要的有:


      gravity决定子view的位置属性,一般有android:gravity="center""left""right"等,特别留意下,该gravity属性可以同时设置多个属性,如android:gravity="center_horizontal|right"。


      ignoreGravity可忽略布局位置的属性。


      说到android:gravity ,不得不说android:layout_gravity决定自身位置的属性,看个例子:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:id="@+id/review_relativelayout"
    android:background="@color/black_overlay"
    android:gravity="center"
    android:layout_gravity="center">
    <View
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:background="@color/white"/>

</RelativeLayout>

      从以下效果图看出:

      RelativeLayout的layout_gravity确定了relative layout在屏幕的位置,RelativeLayout的gravity确定了子view在relative layout的位置。


      如果不设置,任何view都默认在父view的左上方,也就是坐标原点处,如下图:



      4.2 子view的属性

android:layout_above 将当前view的下边缘放置于参照view之上,该属性为参照view的ID

android:layout_alignBaseline当前view与参照view的基线对齐,该属性为参照view的ID

android:layout_alignBottom当前view与参照view的下边界对齐,该属性为参照view的ID

android:layout_alignLeft当前view与参照view的左边界对齐,该属性为参照view的ID

android:layout_alignParenBottom 当前view与父view的下边界对齐,true或false

android:layout_alignParentLeft 当前view与父view的左边界对齐,true或false

android:layout_alignParentRight 当前view与父view的右边界对齐,true或false

android:layout_alignParentTop 当前view与父view的上边界对齐,true或false

android:layout_alignRight 当前view与参照view的右边界对齐,该属性为参照view的ID

android:layout_alignTop 当前view与参照view的上边界对齐,该属性为参照view的ID

android:layout_alignWithParentIfMissing 如果对应的兄弟view找不到的话,就以父view作为参照物,true或false

android:layout_below 将当前view的上边缘放置于参照view之下,该属性为参照组件的ID

android:layout_centerHorizontal 当前view放置到父view的水平居中的位置

android:layout_centerInParent 当前view放置到父view的重心位置

android:layout_centerVertical 当前view放置到父view垂直居中的位置

android:layout_toLeftOf 将当前view的右边缘放置于参照view之下,该属性为参照view的ID

android:layout_toRightOf 将当前view的左边缘放置于参照view之下,该属性为参照view的ID




五、剩下的FrameLayout线性布局、AbsoluteLayout线性布局、TableLayout线性布局,平时较少用,这里暂不做讨论


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值