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