布局过程
- 确定每个View的位置和尺寸
- 作用:为绘制和触摸范围做支持
流程
- 运行前,开发者在xml文件里写入对View的布局要求layout_xxx
- 父View在自己的onMeasure()中,根据开发者在xml中写的对子View的要求,和自己的可用空间,得出对子View的位置和尺寸传给子View,子View保存。
- 子View在自己的onMeasure()中,根据自己的特性算出自己的期望尺寸。
如果是ViewGroup,还会在这里调用每个子View的实际尺寸和位置。 - 父View在子View计算出期望尺寸后,得出子View的实际尺寸和位置
- 子View在自己的layout方法中,将父View传进来的自己的实际尺寸和位置保存。
如果是ViewGroup,还会在onLayout()里调用每个子View的layout()把它们是尺寸位置传给它们。
具体开发
继承已有的View,简单改写它们的尺寸:
- 重写onMeasure()
- 用getMeasureWidth()和getMeasureHeight()获取到测量出的尺寸
- 计算出最终要的尺寸
- 用setMeasuredDimension(width,height)把结果保存
对自定义View完全进行自定义尺寸计算:
- 重写onMeasure()。
- 计算出自己的尺寸。
- 用resolveSize()或者resolveSizeAndState()修正结果。
- 使用setMeasuredDimension(width,height)保存结果。
调用resolveSizeAndState()。
/**
* Version of {@link #resolveSizeAndState(int, int, int)}
* returning only the {@link #MEASURED_SIZE_MASK} bits of the result.
*/
public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
specMode,specSize是父View 对子View的尺寸限制类型和具体限制尺寸。
父布局如果是AT_MOST(表示父View对子View做出了尺寸上限): 会有两种情况:1.父布局可用空间specSize小于子View,这时给子view的空间只能是spaceSize,并且标记子view被压缩。2. 子View就是自己计算出的尺寸size
父布局如果是EXACTLY(表示父View对子View的尺寸做出了精确限制): 子view就是父view的可用空间。
父布局如果是UNSPECIFIED(表示父View对子View没有任何限制):其他情况都是子View计算出的尺寸。
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
* resulting size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be.
* @param measureSpec Constraints imposed by the parent.
* @param childMeasuredState Size information bit mask for the view's
* children.
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
自定义Layout
- 重写onMeasure()
- 遍历每个子View,用measureChildWidthMargins()测量子View。
需要重写generateLayoutParams()并返回MarginLayoutParams才能使用measureChildWidthMargins()方法。
有些子View可能需要重新测量
测量完成后,得出子View的实际位置和尺寸,并暂时保存
- 遍历每个子View,用measureChildWidthMargins()测量子View。
解析一下measureChildWidthMargins()方法。第一行代码解释为什么需要重写generateLayoutParams()。调用getChildMeasureSpec方法,获得子View的MeasureSpec,最后child.measure(childWidthMeasureSpec, childHeightMeasureSpec),子View完成自我测量。重点看一下getChildMeasureSpec方法。
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec()方法,结合开发者设置的LayoutParams中的width和height与父View的剩余可用空间,得出子View的尺寸限制。并使用MeasureSpec.makeMeasureSpec()来返回结果。
size为父View可用空间(父布局的总空间减去已用空间)。
childDimension就是我们在xml中填写的layout_width中的数据。这就是开发者,自己认为需要的尺寸。
- 当childDimension为具体的值:无论什么情况resultSize=childDimension,resultMode=MeasureSpec.EXACTLY。及时父View的可用空间没有这么大,也要尊重开发者,让开发者自己觉得是否需要修改父控件。
- 当childDimension为MATCH_PARENT(需要填满父控件):当父View的specMode=MeasureSpec.EXACTLY(给子View确切值),resultSize=size,resultMode = MeasureSpec.EXACTLY;当父View的specMode=MeasureSpec.AT_MOST(给子View一个上限值),resultSize=size,resultMode = MeasureSpec.AT_MOST;当父View的specMode=MeasureSpec.UNSPECIFIED(子View想要多大就多大),那么子View填满父控件就无从谈起,因此就用方案:resultSize=0,resultMode = MeasureSpec.UNSPECIFIED。
- 当childDimension为WRAP_CONTENT(即要求在不超过限制尺寸的情况下,自己选择合适的尺寸):那么当父View的specMode=MeasureSpec.AT_MOST或者MeasureSpec.EXACTLY都是有限制的,resultSize=size,resultMode = MeasureSpec.AT_MOST;当父View的specMode=MeasureSpec.UNSPECIFIED(子View想要多大就多大),同样的道理没有具体上限,resultSize=0,resultMode = MeasureSpec.UNSPECIFIED。
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
- 重写onLayout()
遍历每个子View,调用它们的layout()方法来将位置和尺寸传给它们。
如何成为自定义高手(一)绘制
如何成为自定义高手(二)动画
如何成为自定义高手(三)布局
如何成为自定义高手(四)触摸反馈,事件分发机制
如何成为自定义高手(五)多点触摸
如何成为自定义高手(六)滑动和拖拽
如何成为自定义高手(七)滑动冲突
网友评论