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事件的处理过程也是很重要的,这将另外研究。
注:以上均为个人学习时的见解,如有错误,还望指出