线下布局水平和垂直Android,Android布局其实很简单,就两步

bae9118dd96fc278c39ad4ef6ff54a13.png

最近做了几个自定义布局,就想扯扯Android布局的这些内容

Android布局其实只有两个步骤

第一步,测量各个View的大小。

第二步,把View布局到指定的位置。

测量

先记住测量的起点是View的measure()方法,然后在measure()方法调用onMeasure()方法来自己测量。

所以主要牵涉的二个方法,measure()和onMeasure()。

我们先看一下FrameLayout代码。假设FrameLayout要求被自我测量,那么它就会调用onMeasure()。

//FrameLayout.class

//

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int count = getChildCount();

......

//获取子View,告诉他们“你们要调用自己测量了。调用方法measureChildWithMargins()”

for (int i = 0; i < count; i++) {

final View child = getChildAt(i);

if (mMeasureAllChildren || child.getVisibility() != GONE) {

measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

....

}

}

......

//等到所有的子View测量完毕,就进行设置自己的大小;如果有需要也可能会进行二次测量

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),

resolveSizeAndState(maxHeight, heightMeasureSpec,

childState << MEASURED_HEIGHT_STATE_SHIFT));

}

复制代码//ViewGroup.class

//

protected void measureChildWithMargins(View child,

int parentWidthMeasureSpec, int widthUsed,

int parentHeightMeasureSpec, int heightUsed) {

......

//父亲让子类测量时,调用的就是View的measure()。这里传的两个参数就是父类安排子类的大小

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

复制代码//View.class

//

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

boolean optical = isLayoutModeOptical(this);

if (forceLayout || needsLayout) {

if (cacheIndex < 0 || sIgnoreMeasureCache) {

// measure ourselves, this should set the measured dimension flag back

......

//子类进行自我测量中...

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);

else

setMeasuredDimension(height,height);

}

}

复制代码

第一步,在调用super.onMeasure()方法后其实已经测量出当前View的大小。

第二步,为了使ImageView变成方的又进行了高宽。

第三步,调用setMeasuredDimension(),重新设置了测量的大小。

2.如果就是想自己计算

public class CalculationView extends View {

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//去掉super.onMeasure()方法

//super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//通过一顿计算得出当前View的大小

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; i < count; i++){

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;i

/**

*

* 然后通过自己的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()方法。

//FrameLayout.class

//

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

layoutChildren(left, top, right, bottom, false /* no force left gravity */);

}

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();

......

//对各子View进行布局操作

child.layout(childLeft, childTop, childLeft + width, childTop + height);

}

}

}

复制代码//View.class

//

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()方法。

我叫陆大旭。

一个懂点心理学的无聊程序员大叔。

看完文章无论有没有收获,记得打赏、关注和点赞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值