android 二次绘制 layout,被人忽略的LinearLayout

本文详细剖析了LinearLayout的高度计算机制,涉及weight、match_parent与wrap_content的使用技巧,通过源码解读,揭示了高度计算的逻辑和优化策略,适合自定义View开发者深入理解。
摘要由CSDN通过智能技术生成

一.开篇

LinearLayout的使用比较简单,本身也没有关于事件分发的重写,layout_weight的设计是一个亮眼的地方,了解内部是如何工作的对于自己开发自定义View也会有很多帮助。

当height设置match_parent时,当height设置0时恰好相反,第一张图是match,第二张是0dp,当然wrap也是不一样的。

205a1b98fc437a8a20a89d9245d5fb87.png

二.height设置0时 源码解析

LinearLayout有水平和垂直两种情况,这边分析垂直的情况,View的绘制流程这边也只关注onMeasure,其他情况都比较简单,按照 height==0 分析。

看下代码

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (mOrientation == VERTICAL) {

measureVertical(widthMeasureSpec, heightMeasureSpec);

} else {

measureHorizontal(widthMeasureSpec, heightMeasureSpec);

}

}

复制代码

mOrientation就是XML中设置的,先看下一些局部变量

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

mTotalLength = 0; // 子View的高度之和 不等于LinearLayout的高度

int maxWidth = 0; //

int childState = 0; //

int alternativeMaxWidth = 0; //

int weightedMaxWidth = 0; //

boolean allFillParent = true; //

float totalWeight = 0; //

final int count = getVirtualChildCount();

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

boolean matchWidth = false;

boolean skippedMeasure = false;

final int baselineChildIndex = mBaselineAlignedChildIndex;

final boolean useLargestChild = mUseLargestChild;

int largestChildHeight = Integer.MIN_VALUE;

int consumedExcessSpace = 0;

int nonSkippedChildCount = 0;

....

}

复制代码

再看下如何对这些变量赋值的,分为三次循环

2.1 第一次循环

// See how tall everyone is. Also remember max width.

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

final View child = getVirtualChildAt(i);

if (child == null) {

// child为空 算作高度为0

mTotalLength += measureNullChild(i);

continue;

}

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

// child 不显示的时候跳过

i += getChildrenSkipCount(child, i);

continue;

}

nonSkippedChildCount++;

if (hasDividerBeforeChildAt(i)) {

// 计算分割线的高度加入

mTotalLength += mDividerHeight;

}

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

totalWeight += lp.weight;

// 高度为0 weight大于0的时候为true

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

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

// useExcessSpace为ture 时先不计算

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

skippedMeasure = true;

} else {

if (useExcessSpace) {

lp.height = LayoutParams.WRAP_CONTENT;

}

// 如果这个View之前的View有应用weight的,usedHeight就为0 (这种情况需要二次计算)

否则就是整个父组件的高度

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

measureChildBeforeLayout(child, i, widthMeasureSpec, 0,

heightMeasureSpec, usedHeight);

final int childHeight = child.getMeasuredHeight();

if (useExcessSpace) {

lp.height = 0;

consumedExcessSpace += childHeight;

}

final int totalLength = mTotalLength;

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

lp.bottomMargin + getNextLocationOffset(child));

if (useLargestChild) {

largestChildHeight = Math.max(childHeight, largestChildHeight);

}

}

复制代码

上面这个过程可以看出当有weight的时候 会出现计算子View height过高的问题,当然这只是第一次循环。

2.2 第二次循环

// 这个循环的条件是 useLargestChild为ture 这个需要xml设置

if (useLargestChild &&

(widthMode == MeasureSpec.AT_MOST || widthMode == 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();

if (isExactly) {

mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +

getNextLocationOffset(child);

} else {

final int totalLength = mTotalLength;

mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +

lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));

}

}

}

复制代码

2.3 第三次循环

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

...

// mTotalLength == 0

int heightSize = mTotalLength;

heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);

// heightSize == 父组件高度

heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

int remainingExcess = heightSize - mTotalLength

+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);

if (childWeight > 0) {

// remainingExcess是剩下的空间 用于分配weight的控件

final int share = (int) (childWeight * remainingExcess / remainingWeightSum);

remainingExcess -= share;

remainingWeightSum -= childWeight;

final int childHeight;

// 从这个if的走向可以看出 odp height的时候 按照weight分配 大的分配到大的height

if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {

childHeight = largestChildHeight;

} else if (lp.height == 0 && (!mAllowInconsistentMeasurement

|| heightMode == MeasureSpec.EXACTLY)) {

childHeight = share;

} else {

// share可能为负值

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

}

...

}

复制代码

三.当对于height == match_parent时 源码解析

这边只列出不同之处

3.1 第一次循环

totalHeight = 2*父组件的高度

3.2 第二次循环

一样

3.3 第三次循环

remainingExcess = -父组件的高度

四.当对于height == wrap_content时 源码解析

这边只列出不同之处

4.1 第一次循环

totalHeight 约等于 2*文本的高度

4.2 第二次循环

一样

4.3 第三次循环

remainingExcess = 父组件的高度-2*文本的高度

总结:

1.weight高度公式 :height = 组件原来的高度+weight/weightsum

2.有weight的情况会执行两遍子view的measure

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[被人忽略的LinearLayout]http://www.zyiz.net/tech/detail-136753.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值