通过Android源码分析LinearLayout的layout_weight与weightsum对布局大小的影响
2016-12-05 00:54
549 查看
LinearLayout是Android App开发中最常用的控件之一。特别是当我们要实现几个控件平均分割一定的区域的时候,一般都会通过LinearLayout的layout_weight和weightsum组合实现。要理解layout_weight和weightsum这2个属性对LinearLayout布局大小的影响,最好还是结合源码进行分析。
这里以水平方向为例做个分析:
从以上代码,我们可以得出:LinearLayout会对所有子元素进行2次测量:第一次预测量是为了计算“剩余空间”;第二次测量是根据weight、weightsum计算在“剩余空间”所占的份额,进而创建measurespec进行测量。
我们可以得出:
未指定android:weightSum属性时,权重和=所有子控件的weight之和;
如果指定了android:weightSum属性,权重和=android:weightSum指定的值,而不是子控件weight和;
剩余空间=总宽度-padding-(每个元素的原始宽度+margin+padding)
元素的长度=原始宽度+权重*父视图剩余空间/权重和
这是最常用的情况:
剩余空间=LinearLayout的总宽度,3个view均分
view1Width=view2Width=view3Width=1440/3=480
剩余空间=1440-100*4=1040
view1Width=400+1040/3=746
view2Width=view3Width=1040/3=346
剩余空间=1440-100*4-1440-1440=-1840
view1Width=400-1840/3<0 => 0
view2Width=view3Width=1440-1840/3=827
剩余空间=1440-1440-1440-1440=-2880
view1Width=1440-2880*2/4 = 0
view2Width=view3Width=1440-2880/4=720
另外,提示大家一点的是:可以通过Android Studio的Layout Inspector查看控件的大小
源码分析
LinearLayout有水平和竖直两种方向,在源码中也有对应的2种测量方法:@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }
这里以水平方向为例做个分析:
void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0;//占用长度:所有明确指定大小元素长度总和+margin+padding float totalWeight = 0;//所有子元素的权重和 final int count = getVirtualChildCount(); //一般地,如果LinearLayout的layout_width为match_parent或者指定值, widthmode为EXACTLY;wrap_content则为AT_MOST final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //是否是精确模式 final boolean isExactly = widthMode == MeasureSpec.EXACTLY; int usedExcessSpace = 0; 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;//我们暂且称为“0宽权重模式” //LinearLayout为精确模式并且“0宽权重模式” if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) { if (isExactly) { mTotalLength += lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin); } } else {//wrap_content情况或非“0宽权重模式” if (useExcessSpace) {//“0宽权重模式” lp.width = LayoutParams.WRAP_CONTENT; } final int usedWidth = totalWeight == 0 ? mTotalLength : 0; //第一次预测量子元素 measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth, heightMeasureSpec, 0); final int childWidth = child.getMeasuredWidth(); if (useExcessSpace) {//“0宽权重模式” lp.width = 0; usedExcessSpace += childWidth; } //计算占用长度 if (isExactly) {//精确模式 mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else {//wrap_content情况 final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; int widthSize = mTotalLength; // Check against our minimum width widthSize = Math.max(widthSize, getSuggestedMinimumWidth()); // Reconcile our calculated size with the widthMeasureSpec int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0); widthSize = widthSizeAndState & MEASURED_SIZE_MASK; // 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. int remainingExcess = widthSize - mTotalLength + (mAllowInconsistentMeasurement ? 0 : usedExcessSpace);//注意这里1,计算“剩余空间” if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) { float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;//注意这里2:如果指定了android:weightSum属性,权重和=android:weightSum指定的值 mTotalLength = 0; //开始第二次测量子元素 for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final float childWeight = lp.weight; if (childWeight > 0) { final int share = (int) (childWeight * remainingExcess / remainingWeightSum);//注意这里3:计算子元素在剩余空间占用的份额 remainingExcess -= share; remainingWeightSum -= childWeight; final int childWidth; if (lp.width == 0 && (!mAllowInconsistentMeasurement || widthMode == MeasureSpec.EXACTLY)) { childWidth = share; } else { childWidth = child.getMeasuredWidth() + share; } //根据childWidth创建childWidthMeasureSpec final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(0, childWidth), MeasureSpec.EXACTLY); final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//注意这里4:真正测量子元素 } if (isExactly) { mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; } }
从以上代码,我们可以得出:LinearLayout会对所有子元素进行2次测量:第一次预测量是为了计算“剩余空间”;第二次测量是根据weight、weightsum计算在“剩余空间”所占的份额,进而创建measurespec进行测量。
关于weightsum
从上面注意这里2 源码:float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
我们可以得出:
未指定android:weightSum属性时,权重和=所有子控件的weight之和;
如果指定了android:weightSum属性,权重和=android:weightSum指定的值,而不是子控件weight和;
关于weight
weight是针对剩余空间的分配,而不是针对LinearLayout总空间的分配;剩余空间=总宽度-padding-(每个元素的原始宽度+margin+padding)
元素的长度=原始宽度+权重*父视图剩余空间/权重和
例子
测试手机屏幕宽度1440px, dp=4例1
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="horizontal"> <View android:id="@+id/view1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_blue_light" /> <View android:id="@+id/view2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_green_light" /> <View android:id="@+id/view3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_orange_light" /> </LinearLayout>
这是最常用的情况:
剩余空间=LinearLayout的总宽度,3个view均分
view1Width=view2Width=view3Width=1440/3=480
例2
view1的宽度改为100dp,其他不变... <View android:id="@+id/view1" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_blue_light" /> ...
剩余空间=1440-100*4=1040
view1Width=400+1040/3=746
view2Width=view3Width=1040/3=346
例3
view1的宽度改为100dp,view2/view3宽度为match_parent或wrap_content<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="horizontal"> <View android:id="@+id/view1" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_blue_light" /> <View android:id="@+id/view2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_green_light" /> <View android:id="@+id/view3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_orange_light" /> </LinearLayout>
剩余空间=1440-100*4-1440-1440=-1840
view1Width=400-1840/3<0 => 0
view2Width=view3Width=1440-1840/3=827
例4
view1/view2/view3宽度为match_parent或wrap_content,view1的layout_weight=2<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="horizontal"> <View android:id="@+id/view1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="2" android:background="@android:color/holo_blue_light" /> <View android:id="@+id/view2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_green_light" /> <View android:id="@+id/view3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/holo_orange_light" /> </LinearLayout>
剩余空间=1440-1440-1440-1440=-2880
view1Width=1440-2880*2/4 = 0
view2Width=view3Width=1440-2880/4=720
另外,提示大家一点的是:可以通过Android Studio的Layout Inspector查看控件的大小
相关文章推荐
- Android 布局之LinearLayout 子控件weight权重的作用详析
- 线性布局(LinearLayout)下layout_weight用法分析
- Android按比例布局之layout_weight和weightSum的使用
- Android线性布局LinearLayout,及盒模型,weight权值,ScrollView和HorizontalScrollView【内容溢出(内容太多,高度/宽度大于能显示的最大高度/宽度)】
- Android LinearLayout布局嵌套及权重weight的使用
- Android布局中的layout_weight和weightSum属性的详解及使用
- Android 布局之LinearLayout 子控件weight权重的作用详析
- Android_按比例布局layout_weight和weightSum
- android布局之LinearLayout, layout_weight很有用
- Android LinearLayout布局的layout_weight属性探究
- android之线性布局LinearLayout以及weight权重使用
- android布局之LinearLayout, layout_weight
- Android LinearLayout布局中 layout_weight解析
- android布局之LinearLayout, layout_weight
- Android 布局之LinearLayout 子控件weight权重的作用详析
- Android 布局之LinearLayout 子控件weight权重的作用详析(转)
- Android应用开发原理之从源码分析看Linearlayout、Relativelayout,Framelayout的布局差别(Relativelayout分析)
- Android 布局之LinearLayout 子控件weight权重的作用详析
- Android08之LinearLayout布局讲解(权重分析*)
- Android中设置半个屏幕大小且居中的按钮布局 (layout_weight属性)