2024年LinearLayout源码详解(1),Android面试题汇总

最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

录播视频图.png

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// 如果当前的LinearLayout不是EXACTLY模式,且子View的weight大于0,优先会把当前LinearLayout的全部可用高度用于子View测量
// 我们在代码中也可以很清晰的看到,在getChildMeasureSpec()中,子控件需要把父控件的padding,自身的margin以及一个可调节的量三者一起测量出自身的大小。那么假如在测量某个子控件之前,weight一直都是0,那么该控件在测量时,需要考虑在本控件之前的总高度,来根据剩余控件分配自身大小。而如果有weight,那么就不考虑已经被占用的控件,因为有了weight,子控件的高度将会在后面重新赋值。

// 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).
//确定这个孩子想要多大。如果这个或前面的孩子给了一个权重,那么我们允许它使用所有可用的空间
//(如果需要,我们将在稍后缩小内容)。
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);

// 重置子控件高度,然后进行精确赋值
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// Restore the original height and record how much space
// we’ve allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
//恢复原来的高度,并记录我们分配给 excess-only children 的空间大小,以便我们能够准确地匹配测量的行为。
lp.height = 0;
consumedExcessSpace += childHeight;
}

final int totalLength = mTotalLength;
// getNextLocationOffset返回的永远是0,这里是加上子控件的margin值
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
// 如果设置了measureWithLargestChild属性为true,获取最高子控件的高度
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}

useLargestChild 可以通过 xml 属性 android:measureWithLargestChild 设置的,含义是所有带权重属性的View都会使用最大View的最小尺寸

//useLargestChild 属性指定
//所以接下来根据 largestChildHeight 重新计算高度
if (useLargestChild &&
(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
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}

使用 largestChildHeight 重新计算mTotalLength,在代码中也可以看到,这个属性只在wrap_content情况下生效

到第二次测量中间还有一些其他的计算,就不一一去看了,直接看第二次测量

// 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.
// 重新计算有 weight 属性的 childView 大小,
// 如果还有可用的空间,则扩展 childView,计算其大小
// 如果 childView 超出了 LinearLayout 的边界,则收缩 childView
int remainingExcess = heightSize - mTotalLength

  • (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
    if (skippedMeasure
    || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
    // 根据 mWeightSum 计算得到 remainingWeightSum,mWeightSum 是通过
    // android:weightSum` 属性设置的,totalWeight 是通过第一次 for 循环计算得到的
    float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

mTotalLength = 0;

for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}

final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
// 这是设置了 weight 的情况下,最重要的一行代码
// remainingExcess 剩余高度 * ( childView 的 weight / remainingWeightSum)
// share 便是此 childView 通过这个公式计算得到的高度,
// 并重新计算剩余高度 remainingExcess 和剩余权重总和 remainingWeightSum
if (childWeight > 0) {
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;

final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == MeasureSpec.EXACTLY)) {
//如果是当前LinearLayout的模式是EXACTLY
//那么这个子View是没有被测量过的,就需要测量一次
//如果不是EXACTLY的,在第一次循环里就被测量一些了
// This child needs to be laid out from scratch using
// only its share of excess space.
childHeight = share;
} else {
//如果是非EXACTLY模式下的子View就再加上
//weight分配占比*剩余高度
// This child had some intrinsic height to which we
// need to add its share of excess space.
childHeight = child.getMeasuredHeight() + share;
}

final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
//重新测量一次,因为高度发生了变化
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

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

举例

在weight计算方面,我们可以清晰的看到,weight为何是针对剩余空间进行分配的原理了。 我们打个比方,假如现在我们的LinearLayout的weightSum=10,总高度100,有两个子控件(他们的height=0dp),他们的weight分别为2:8。

那么在测量第一个子控件的时候,可用的剩余高度为100,第一个子控件的高度则是100*(2/10)=20,接下来可用的剩余高度为80

我们继续第二个控件的测量,此时它的高度实质上是80*(8/8)=80

到目前为止,看起来似乎都是正确的,但关于weight我们一直有一个疑问:**就是我们为子控件给定height=0dp和height=match_parent时我们就会发现我们的子控件的高度比是不同的,前者是2:8而后者是调转过来变成8:2 **

对于这个问题,我们不妨继续看看代码。

接下来我们会看到这么一个分支:

if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { } else {}

首先我们不管heightMode,也就是父类的测量模式,剩下一个判定条件就是lp.height,也就是子类的高度。

既然有针对这个进行判定,那就是意味着肯定在此之前对child进行过measure,事实上,在这里我们一早就对这个地方进行过描述,这个方法正是measureChildBeforeLayout()。

还记得我们的measureChildBeforeLayout()执行的先行条件吗

YA,just u see,正是不满足(LinearLayout的测量模式非EXACTLY/child.height0/child.weight/child.weight>0)之中的child.height0

因为除非我们指定height=0,否则match_parent是等于-1,wrap_content是等于-2.

在执行measureChildBeforeLayout(),由于我们的child的height=match_parent,因此此时可用空间实质上是整个LinearLayout,执行了measureChildBeforeLayout()后,此时的mTotalLength是整个LinearLayout的大小

回到我们的例子,假设我们的LinearLayout高度为100,两个child的高度都是match_parent,那么执行了measureChildBeforeLayout()后,我们两个子控件的高度都将会是这样:

child_1.height=100

child_2.height=100

mTotalLength=100+100=200

在一系列的for之后,执行到我们剩余空间:

int delta = heightSize - mTotalLength;

(delta=100[linearlayout的实际高度]-200=-100)

没错,你看到的的确是一个负数。

接下来就是套用weight的计算公式:

share=(int) (childExtra * delta / weightSum)

文末

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家

这里笔者分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

【视频教程】

天道酬勤,只要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

要你想,大厂offer并不是遥不可及!希望本篇文章能为你带来帮助,如果有问题,请在评论区留言。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值