先记住测量的起点是View的measure()方法,然后在measure()方法调用onMeasure()方法来自己测量。
所以主要牵涉的二个方法,measure()和onMeasure()。
我们先看一下FrameLayout代码。假设FrameLayout要求被自我测量,那么它就会调用onMeasure()。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
......
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
......
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (forceLayout || needsLayout) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
这里的FrameLayout是继承了ViewGroup。
View中的onMeasure()主要是用来计算自己的尺寸。
ViewGroup中的onMeasure()主要是用来调用子类的measure()进行自我测量。同时计算自己的尺寸。
在onMeasure()自己计算尺寸的时候一定要注意是是需要要符合父类的限制的,这个限制就是onMeasure()方法中传入的widthMeasureSpec和heightMeasureSpec。这两个参数中存储着两个信息,大小和限制的类型。
获取大小和限制类型的方式
final int specMode = MeasureSpec.getMode(measureSpec)
final int specSize = MeasureSpec.getSize(measureSpec)
限制的类型一个是三个
MeasureSpec.AT_MOST:老爸告诉你,你只能这么大
MeasureSpec.EXACTLY:老爸告诉你,你就这么大,不能讨教还价
MeasureSpec.UNSPECIFIED:老爸告诉你,你想这么着就怎么着
如何设置当前View的尺寸
一般计算完当前View的尺寸后可以通过调用resolveSize()或resolveSizeAndState()方法计算出当前的大小用setMeasuredDimension
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);
来,我们举几个例子
1.如果我希望ImageView一直是方
public class SquareImageView extends ImageView {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (width > height)
setMeasuredDimension(width,width);
setMeasuredDimension(height,height);
第一步,在调用super.onMeasure()方法后其实已经测量出当前View的大小。
第二步,为了使ImageView变成方的又进行了高宽。
第三步,调用setMeasuredDimension(),重新设置了测量的大小。
2.如果就是想自己计算
public class CalculationView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = ......
int height = ......
width = resolveSize(width,widthMeasureSpec);
height = resolveSize(height,heightMeasureSpec);
setMeasuredDimension(width,height);
如果是纯粹的自己重新计算大小,就不需要调用super.onMeasure()。
第一步,计算自己应该有的大小。
第二步,用resolveSize方法结合父类的限制获取大小。
第三步,调用setMeasuredDimension()设置大小。
3.如果是对ViewGroup进行计算一般的流程
public class CalculationView extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount()
for (int i = 0
View view = getChildAt(i)
LayoutParams lp = view.getLayoutParams()
int widthMode = MeasureSpec.getMode(widthMeasureSpec)
int widthSize = MeasureSpec.getSize(widthMeasureSpec)
int heightMode = MeasureSpec.getMode(heightMeasureSpec)
int heightSize = MeasureSpec.getSize(heightMeasureSpec)
* 进行一顿就算操作得出各种结论
* 比如,最宽的是多宽,最高的是多高等信息
* 设置当前View的大小
setMeasuredDimension(measuredWidth,measuredHeight)
for (int i=0
* 然后通过自己的widthMode,widthSize,heightMode,heightSize和lp进行各种计算
* 比如最终确定高是height,宽是width。
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY)
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY)
view.measure(childWidthMeasureSpec,childHeightMeasureSpec)
在继承ViewGroup的类中比继承View的类多一个让子类调用measure()的步骤
第一步,获取子类的大小,结合自己布局的规则进行测量和保存必要的数据。
第二步,通过第一步的计算已经知道了自己的大小,用setMeasuredDimension()方法进行设置。
第三步,对各个子类进行限制并且调用view.measure()方法。
黄金分割线
就上面所有的内容只是测量,仅仅是测量,还没有到真正布局的步骤。
黄金分割线下就是布局了。
先记住布局的起点是View的layout()方法,然后layout()方法中会调用onLayout()方法。
我们先看看FrameLayout代码,假设要执行布局就会调用onLayout()方法。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false );
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
......
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
......
child.layout(childLeft, childTop, childLeft + width, childTop + height);
public void layout(int l, int t, int r, int b) {
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
//View.class
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
//设置View的位置大小
mLeft = left
mTop = top
mRight = right
mBottom = bottom
return changed
布局的逻辑其实很简单,就是递归调用onLayout()和layout()方法
第一步,在onLayout方法中进行必要的计算,主要是针对当前布局。
第二步,获取每一个View的LayoutParams的设置参数和大小,一顿计算和操作。
第三步,调用子View的layout()方法,调用对View位置起作用的就是setFrame()方法
第四步,继续调用子类的onLayout()方法。
在View的onLayout()方法是空的,ViewGroup的onLayout是一个抽象方法。
测量涉及measure()和onMeasure()方法。
measure()主要的作用就是优化和调用onMeasure()方法。
重要的是onMeasure()方法。根据不同需求在方法中一顿计算、测量、设置测量后的大小并且可能会调用子类的measure()方法。
记住MeasureSpec类和里面的方法、参数。记住View.resolveSize()。
布局涉及layout()和onLayout()方法。
layout()主要作用就是设置当前View的位置和调用onLayout()方法。
重要的是onLayout()方法。根据不同布局需求利用子类的LayoutParams参数和大小在方法中一顿计算,然后调用子类的layout()方法。
我叫陆大旭。
一个懂点心理学的无聊程序员大叔。
看完文章无论有没有收获,记得打赏、关注和点赞!