线性布局html,线性布局和相对布局的实现原理

前言

对于中高级的工程师来说,有一定的阅读源码的经验已经必备技能之一了,尤其是 LinearLayout 和 RelativeLayout,我们先准备一下要掌握的知识点:

LinearLayout 的 Weight 是如何实现的?

LinearLayout 的 Weight 对于测量过程增加了哪些实现成本?

LinearLayout 的 Weight 真的会导致需要测量两次吗?

RelativeLayout 的 Measure 过程为什么会有两次?

RelativeLayout 的 Padding 和 Margin 对于居中属性为什么没有影响?

RelativeLayout 在版本 4.2,4.3 和 4.4 做了哪些调整和适配?

还有很多其他的问题困扰着我们,很多技术分享文章都只是讲大概的流程,被问到细节的时候,我们还是会一脸懵逼,不知所措。

这一篇 Chat 我们来详细的分析一下 LinearLayout 和 RelativeLayout 的实现原理,是一句一句的分析,真的十分详细的分析。

LinearLayout

LinearLayout 的特性是线性布局,weight 属性是他的专属利器,可以实现比例布局。LinearLayout 可以设置横向或者是竖向,这里以竖向布局作为分析的目标。

onMeasure

首先我们看一下 LinearLayout 的 onMeasure 方法:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (mOrientation == VERTICAL) {

measureVertical(widthMeasureSpec, heightMeasureSpec);

} else {

measureHorizontal(widthMeasureSpec, heightMeasureSpec);

}

}

非常的简单,根据设置的方向调用指定方向的测量方法,因为我们这次分析的目标是竖向,所以看 measureVertical 方法。其实水平方法的测量原理和竖向是一样的。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

// 计算的总高度

mTotalLength = 0;

// 最大宽度

int maxWidth = 0;

int childState = 0;

// 记录没有使用weight的child最大宽度

int alternativeMaxWidth = 0;

// 记录使用了weight的child的最大宽度

int weightedMaxWidth = 0;

// 是否所有的child的宽度都是match_parent

boolean allFillParent = true;

// 计算child的总weight

float totalWeight = 0;

// 子View的数量

final int count = getVirtualChildCount();

// 自身宽度的测量模式

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

// 自身高度的测量模式

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

// 子View是否需要和parent的宽度一致

boolean matchWidth = false;

// 是否要跳过measure过程

boolean skippedMeasure = false;

// 基线的child的位置

final int baselineChildIndex = mBaselineAlignedChildIndex;

// 是否使用最大的child的高度

final boolean useLargestChild = mUseLargestChild;

// 上一个child的高度

int largestChildHeight = Integer.MIN_VALUE;

// 记录消耗的扩展的空间,给weight服务

int consumedExcessSpace = 0;

// 记录总共测量了child的个数

int nonSkippedChildCount = 0;

...

}

打开 measureVertical 方法,一上来就是一堆临时变量,我们先记住他们的作用,便于之后的分析。

然后开始第一次的 for 循环:

// 开始第一次的循环操作

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

final View child = getVirtualChildAt(i);

// 如果child是空的,跳过,measureNullChild等于0

if (child == null) {

mTotalLength += measureNullChild(i);

continue;

}

// 如果child是隐藏状态,跳过

if (child.getVisibility() == View.GONE) {

i += getChildrenSkipCount(child, i);

continue;

}

// 测量的child的个数+1

nonSkippedChildCount++;

// 如果在child之前需要绘制divider,加上divider的宽度

if (hasDividerBeforeChildAt(i)) {

mTotalLength += mDividerHeight;

}

// 得到child的布局信息

final LayoutParams lp = (LayoutParams) child.getLayoutParams();

// 计算总weight

totalWeight += lp.weight;

// 这里判断是否使用weight

final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;

// 如果parent高度是是match_parent或者固定值,child使用了weight

if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {

// 记录当前的高度最大值

final int totalLength = mTotalLength;

// 总高度加上子View的上边距和下边距,和目前的高度对比取最大值

// 这里并没有加child的高度,会在之后再次计算weight的占比的时候设置

mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);

// 有child跳过了测量过程,之后会进入到weight的测量过程

skippedMeasure = true;

} else {

// 如果使用了weight,先把child的高度设置成wrap_content

// 先测量child的最小高度

if (useExcessSpace) {

lp.height = LayoutParams.WRAP_CONTENT;

}

// 如果目前还没child使用weight,高度使用mTotalLength,否则使用0

// usedHeight表示已使用的高度,之后parent的测量高度会减去这个高度,作为child的高度最大值限制

final int usedHeight = totalWeight == 0 ? mTotalLength : 0;

// 测量child

measureChildBeforeLayout(child, i, widthMeasureSpec, 0,

heightMeasureSpec, usedHeight);

// 得到child的高度

final int childHeight = child.getMeasuredHeight();

// 如果child使用了weight

if (useExcessSpace) {

lp.height = 0;

// 增加要扩展的空间

consumedExcessSpace += childHeight;

}

// 计算总高度

final int totalLength = mTotalLength;

mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +

lp.bottomMargin + getNextLocationOffset(child));

// 如果使用了最大child高度,和之前测量的child的高度对比,取最大值

if (useLargestChild) {

largestChildHeight = Math.max(childHeight, largestChildHeight);

}

}

...

}

上面的代码主要做了两个工作:

如果 LinearLayout 的高度是 match_parent 或者固定值,child 使用了 weight,直接跳过了 child 的测量过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值