- CompoundButton 源码分析
- LinearLayout 源码分析
- SearchView 源码解析
- LruCache 源码解析
- ViewDragHelper 源码解析
- BottomSheets 源码解析
- Media Player 源码分析
- NavigationView 源码解析
- Service 源码解析
- Binder 源码分析
- Android 应用 Preference 相关及源码浅析 SharePreferences 篇
- ScrollView 源码解析
- Handler 源码解析
- NestedScrollView 源码解析
- SQLiteOpenHelper/SQLiteDatabase/Cursor 源码解析
- Bundle 源码解析
- LocalBroadcastManager 源码解析
- Toast 源码解析
- TextInputLayout
- LayoutInflater 和 LayoutInflaterCompat 源码解析
- TextView 源码解析
- NestedScrolling 事件机制源码解析
- ViewGroup 源码解析
- StaticLayout 源码分析
- AtomicFile 源码解析
- AtomicFile 源码解析
- Spannable 源码分析
- Notification 之 Android 5.0 实现原理
- CoordinatorLayout 源码分析
- Scroller 源码解析
- SwipeRefreshLayout 源码分析
- FloatingActionButton 源码解析
- AsyncTask 源码分析
- TabLayout 源码解析
3.2 weight
3.2.1 weight 的再次测量
在上面的代码中,LinearLayout 做了针对没有 weight 的工作,在这里主要是确定自身的大小,然后再针对 weight 进行第二次测量来确定子控件的大小。
我们接着看下面的代码:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { //...接上面 // 下面的这一段代码主要是为 useLargestChild 属性服务的,不在本文主要分析范围,略过 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerHeight; } 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)); } } // Add in our padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Reconcile our calculated size with the heightMeasureSpec int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; }
上面这里是为 weight 情况做的预处理。
我们略过 useLargestChild 的情况,主要看看 if 处理外的代码。在这里,我没有去掉官方的注释,而是保留了下来。从中我们不难看出 heightSize 做了两次赋值,为何需要做两次赋值。
因为我们的布局除了子控件,还有自己本身的 background,因此这里需要比较当前的子控件的总高度和背景的高度取大值。
接下来就是判定大小,我们都知道测量的 MeasureSpec 实际上是一个 32 位的 int,高两位是测量模式,剩下的就是大小,因此 heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
作用就是用来得到大小的精确值(不含测量模式)
接下来我们看这个方法里面第二占比最大的代码:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { //...接上面 //算出剩余空间,假如之前是 skipp 的话,那么几乎可以肯定是有剩余空间(同时有 weight)的 int delta = heightSize - mTotalLength; if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { // 限定 weight 总和范围,假如我们给过 weighSum 范围,那么子控件的 weight 总和受此影响 float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child.getVisibility() == View.GONE) { continue; } LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { // 全篇最精华的一个地方。。。。拥有 weight 的时候计算方式,ps:执行到这里时,child 依然还没进行自身的 measure // 公式 = 剩余高度*(子控件的 weight/weightSum),也就是子控件的 weight 占比*剩余高度 int share = (int) (childExtra * delta / weightSum); // weightSum 计余 weightSum -= childExtra; // 剩余高度 delta -= share; final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width); if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { int childHeight = child.getMeasuredHeight() + share; if (childHeight < 0) { childHeight = 0; } child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); } else { child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, MeasureSpec.EXACTLY)); } 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)); } mTotalLength += mPaddingTop + mPaddingBottom; } // 没有 weight 的情况下,只看 useLargestChild 参数,如果都无相关,那就走 layout 流程了,因此这里忽略 else { alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth); 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)); } } } } }
3.2.2 weight 的两种情况
这次我的注释比较少,主要是因为需要有一大段的文字来描述。
在 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.height==0/child.weight/child.weight>0)之中的 child.height==0
。
因为除非我们指定 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)
即: share = -100(2/10) = -20;
然后走到我们所说的 if/else 里面
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { // child was measured once already above... // base new measurement on stored values int childHeight = child.getMeasuredHeight() + share; if (childHeight < 0) { childHeight = 0; } child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); }
我们知道 child.getMeasuredHeight()=100
,接着这里有一条 int childHeight = child.getMeasuredHeight() + share;
,这意味着我们的 childHeight=100+(-20)=80;
。
接下来就是走 child.measure,并把 childHeight 传进去,因此最终反馈到界面上,我们就会发现,在两个 match_parent 的子控件中,weight 的比是反转的。
接下来没什么分析的,剩下的就是走 layout 流程了,对于 layout 方面,要讲的其实没什么东西,毕竟基本都是模板化的写法了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论