Android学习笔记——LinearLayout源码分析

LinearLayout继承ViewGroup,是最常用的布局之一,可以对其包含的子视图进行水平或垂直方向布局。

一、构造函数

public LinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.LinearLayout, defStyle, 0);
        //设置方向(水平或者垂直)
        int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
        if (index >= 0) {
            setOrientation(index);
        }
        //设置对齐方式
        index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
        if (index >= 0) {
            setGravity(index);
        }
        //设置子视图基准线对齐
        boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
        if (!baselineAligned) {
            setBaselineAligned(baselineAligned);
        }
        //将剩余空间的多少分配给含有layout_weight属性的子视图
        mWeightSum = a.getFloat(R.styleable.LinearLayout_weightSum, -1.0f);
        //基准线对齐的子视图序号
        mBaselineAlignedChildIndex =
                a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);

        mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);

        setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider));
        mShowDividers = a.getInt(R.styleable.LinearLayout_showDividers, SHOW_DIVIDER_NONE);
        mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayout_dividerPadding, 0);

        a.recycle();
    }

对于一个View或者ViewGroup来说,最重要的是onMeasure、onDraw、onLayout三个函数以及触摸事件相关函数,下面就来看一下这些函数在LinearLayout中的实现。

二、onMeasure

@Override
    protected void onMeasure(int widthMeasureSpec, int  heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            //measure垂直方向
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            //measure水平方向
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

这里仅分析垂直方向:

/**
     * 当orientation为Vertical时调用此函数测量子视图的大小
     *
     * @param widthMeasureSpec 父视图规定的水平方向需要的空间
     * @param 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();
        // 获得水平方向和垂直方向的测量模式
        // MeasureSpec.getMode返回UNSPECIFIED、AT_MOST、EXACTLY三种模式
        // UNSPECIFIED:父视图没有限制子视图大小,子视图可以获得它想要的任何尺寸
        // AT_MOST:子视图可以获得不超过父视图指定的最大尺寸的大小,例如子视图使用WRAP_CONTENT时,子视图的大小会随着内容改变,只要不超过父视图规定的大小即可
        // EXACTLY:父视图已经为子视图决定了确定的尺寸,子视图只能获得父视图规定的尺寸
        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;

        // 获得每个子视图的高度,以及最大高度
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
                // getChildrenSkipCount总是返回0
               i += getChildrenSkipCount(child, i);
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {
                // 如果设置了分割线,需要把分割线的高度加到总高度中
                mTotalLength += mDividerHeight;
            }
            // 获得子视图的布局参数
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
            // 累计weight
            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // 如果LinearLayout的垂直方向测量模式是EXACTLY,即确定值,且子视图的高度为0,weight大于0,则先将总高度加上子视图的topMargin和bottomMargin,并设置skippedMeasure(暂时跳过测量标识)为true
                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) {
                    // 如果垂直方向测量模式为UNSPECIFIED或AT_MOST,同时子视图想要尽量获取可用的剩余空间,把子视图的高度改为WRAP_CONTENT,这样子视图的最终高度就不会是0
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                // 这个函数最后会调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)测量出子视图要占用多大空间,并设置子视图的mMeasuredWidth和mMeasuredHeight
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);

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

                // 获得子视图的mMeasuredHeight属性
                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);
                }
            }

            // 计算子视图baseline的偏移量
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            // 如果要为baseline指定子视图索引,只有在此子视图之上的视图没有设置weight属性时才有效
            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) {
                //如果LinearLayout宽度不是已确定的,如wrap_content,而子视图是MATCH_PARENT,  
                //则做标记matchWidth=true; matchWidthLocally = true;  
                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());
            // 子视图宽度是否都为MATCH_PARENT
            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                //如设置了weigh属性,则子视图的宽度需要在父视图确定后才能确定。这里并不是真实的宽度  
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }
        // for循环结束

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

        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;
            // 如果设置了useLargestChild属性,且LinearLayout的垂直方向测量模式是AT_MOST或UNSPECIFIED,重新测量总高度,useLargestChild属性会使所有带weight属性的子视图具有最大子视图的最小尺寸
            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();

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

        // 加上LinearLayout本身的上下内边距
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // 和建议的最小值进行比较,将高度更新为当前测量高度和建议最小高度的大者
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // 把测量出来的高度与测量模式进行匹配,得到最终的高度
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

        // 为带有weight属性的子视图分配空间
        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) {
                    // 每个子视图占用的空间大小是:剩余空间*weight/weightSum
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;
                    // 获取子视图测量规格
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);

                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        // 上面已经测量过这个子视图,把上面测量的结果加上根据weight分配的大小
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }

                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        // 上面测量的时候被跳过,那么在这里进行测量    
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }

                    // 合并测量状态
                    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));
            }

            // 这里得到最终高度
            mTotalLength += mPaddingTop + mPaddingBottom;
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            // 使所有具有weight属性 视图都和最大子视图一样高,子视图可能在上面已经被测量过一次
            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;

        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        // 设置测量完的宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            // 使宽度一致
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

三、onDraw

LinearLayout没有什么特别的需要绘制,主要就是绘制一下divider

@Override
    protected void onDraw(Canvas canvas) {
        if (mDivider == null) {
            return;
        }

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

    void drawDividersVertical(Canvas canvas) {
        final int count = getVirtualChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);

            if (child != null && child.getVisibility() != GONE) {
                if (hasDividerBeforeChildAt(i)) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    final int top = child.getTop() - lp.topMargin - mDividerHeight;
                    drawHorizontalDivider(canvas, top);
                }
            }
        }

        if (hasDividerBeforeChildAt(count)) {
            final View child = getVirtualChildAt(count - 1);
            int bottom = 0;
            if (child == null) {
                bottom = getHeight() - getPaddingBottom() - mDividerHeight;
            } else {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                bottom = child.getBottom() + lp.bottomMargin;
            }
            drawHorizontalDivider(canvas, bottom);
        }
    }

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

    /**
     * 在布局过程中确定子视图的位置
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;

        // 父视图宽度
        final int width = right - left;
        int childRight = width - mPaddingRight;

        // 子视图可用宽度
        int childSpace = width - paddingLeft - mPaddingRight;

        final int count = getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) {
           case Gravity.BOTTOM:
               // 父视图的gravity为BOTTOM时,子视图top相对于父视图的位置=父视图顶部内边距+父视图高度-子视图总高度
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;

           case Gravity.CENTER_VERTICAL:
               // 父视图的gravity为CENTER_VERTICAL时,计算出子视图top相对于父视图的位置,父视图的顶部间距+父视图高度的一半-子视图总高度的一半
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;

           case Gravity.TOP:
           default://默认情况子视图top=父视图的顶部内边距
               childTop = mPaddingTop;
               break;
        }

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                // 子视图占用空间,根据子视图设置的layout_gravity计算子视图左边位置
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();

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

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        // 子视图layout_gravity为CENTER_HORIZONTAL,子视图相对于父视图的left=父视图左边内边距+子视图可用宽度的一半-子视图宽度的一半+子视图左外边距-子视图右外边距
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        // 子视图layout_gravity为RIGHT,子视图相对于父视图的left=子视图右边界-子视图宽度-子视图右边外边距
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        // 子视图layout_gravity为LEFT,即默认情况,子视图相对于父视图的left=父视图左边内边距+子视图左边外边距
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    // 如果设置了divider,子视图top还要再加上divider高度,即需要下移
                    childTop += mDividerHeight;
                }
                // 还要再下移子视图顶部外边距的高度
                childTop += lp.topMargin;
                // setChildFrame最终会调用child.layout(left, top, left + width, top + height)设置子视图位置
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                // 移动子视图的top到下一个子视图的位置
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

至此,ViewGroup绘制过程中最重要的三个函数已经分析完毕,从中可以看出,onMeasure函数最为复杂,它负责测量每个子视图的尺寸,其次是onLayout函数,它负责确定子视图相对于父视图的左边偏移量和顶部偏移量,onDraw最为简单,因为这只是一个容器,不需要有具体的外观。除了这三个绘制过程的函数很重要之外,touch事件的处理过程也是很重要的,这将另外研究。
注:以上均为个人学习时的见解,如有错误,还望指出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值