需求场景
- 一行中有3个TextView处于左边,以此排序。单独有一个TextView处于屏幕的最右边。然后左边优先级最高,最大展示一行,超过一行显示三点和把自身右边的TextView挤出屏幕外。
自身想法
创建一个LinearLayout父布局,把前3个TextView(t1,t2,t3)放进LinearLayout里面,然后加一个View(View:layout_width = 0dp,layout_weight = 1),然后再到TextView(t4)。在布局层面上确实可以达到效果,t4处于屏幕的最右边。t1,t2,t3不管显示和隐藏,t4都能处于屏幕最右边。
-
效果如下图
问题
-
上面虽然把整体布局实现了,但是问题来了,前面三个TextView超长的时候都能限制在LinearLayout的大小内,但是最右边的TextView超长后直接比LinearLayout的宽度还要大,宽度会延伸到屏幕的最右边。
效果图
解决问题
- 这里用LinearLayout的Horizontal方向为例子讲解。首先LinearLayout的onMeasure方法里会用for循环逐个取出childView,每一次取出的childView都会取其LayoutParams,目的是为了计算整体的weight值大小。
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
……
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
//这个if就是对应自我深思第三点的第三中方法,width = 0,weight > 0
……
}else{
……
//记录下前面childView已用的宽度(如果是垂直方法,应该是记录已用的高度)
//1.如果当前的totalWeight为0,用parentWidth - 已用宽度 = 剩余宽度,把剩余宽度告知子View
//2.如果当前的totalWeight不为0,直接用parentWidth宽度告知子View
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);
final int childWidth = child.getMeasuredWidth();
}
}
- measureChildBeforeLayout方法
measureChildBeforeLayout是根据输入的size和mode,最后拼成一对childWidthMeasureSpec和childHeightMeasureSpec,重新塞给childView去计算宽高。
//最终走到这里
/***
*** parentWidthMeasureSpec和parentHeightMeasureSpec就是父size和父mode的 | 操作,不懂自行再去查询。
*** widthUsed: 已用宽度 heightUsed:已用高度
***/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
- 从上面分析可以很清楚看到为什么我预期和结果不一致了。LinearLayout只要遇到子View设置了layout_weight属性,就会把前面占用的大小清0,后面的View就会根据自身测量大小,LinearLayout不做限制,要多大给多大。因为我的TextView都是wrap_content,其大小是字体Text的大小;所以就会导致如果字体很长,我的最右边的TextView就会无限延长,超出了LinearLayout自身的大小了。这就是根本原因,代码原因如下
//只要有子View设置了layout_weight属性,从该子View起,后面的子View都得不到已经占用的大小信息,因为强制设置成了0.
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
自我深思
- 对layout_weight内部功能如何实现不够清晰,只知道可以用于占满剩余的空间。
- 遇到问题多debug看看源码,其实看了源码后你会觉得自己基础知识都能巩固了不少。
- 如果要解决这个问题:一、最右边TextView手动限制大小,maxEms或者width = “xxxdp”。二、换一种父布局实现效果。三、去除里面的View,将最右边的TextView设置成width = 0dp,layout_weight = 1,gravity = right,也能实现对应效果。