本文主要介绍LinearLayout中分隔线Weight的使用方法
涉及到以下几点内容:
在讲解measureWithLargestChild和weight使用方法之前必须先来简单了解下布局的绘制过程。
Android开发文档中有如下描述:
“绘制布局由两个遍历过程组成:测量过程和布局过程。测量过程由measure(int,int)方法完成,该方法从上到下遍历视图树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当measure方法遍历结束时,每个视图都保存了各自的尺寸信息。第二个过程由layout(int,int,int,int)方法完成,该方法也是由上而下遍历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所有子视图的位置信息。”
/** *采用递归方法遍历所有view * * @param viewGroup */
public void traversalView(ViewGroup viewGroup) {
//求当前ViewGroup下子视图的总数量
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
//获取第I个子视图
View view = viewGroup.getChildAt(i);
//如果子视图属于ViewGroup,有可能其下仍然含有子视图,继续判断
if (view instanceof ViewGroup) {
traversalView((ViewGroup) view);
} else {
doView(view);
}
}
}
/** * 处理view * * * @param view */
private void doView(View view) {
//TODO:something
}
这里有两个概念:
1.测量高度、宽度
在测量过程中得出的高度和宽度,对应getMeasuredWidth()方法
2.实际高度、宽度
布局完成后得到的高度和宽度,对应getWidth()方法
两者区别:
getWidth(): View在布局完成后整个View的实际宽度。
getMeasuredWidth(): 对View上的内容进行测量后得到的View占据的宽度,有可能大于实际宽度
值得注意的陷进:如果在Activity的onCreate()或者Fragment的onCreateView()中直接调用View的getHeight和getWidth方法,会发现返回值都是0.
首先分析为什么在onCreate()方法中读取视图的尺寸会返回0.当onCreate()方法被调用时,会通过LayoutInflater将XML布局文件填充到ContenView。填充过程只包括创建视图,却不包括设置其大小。那么,视图的大小是在何时指定的呢?
通过“布局绘制过程”可以得出如下结论:
只有在整个布局绘制完毕后,视图才能得到自身的高和宽,这个过程发生在onCreate()方法之后,因此,在此之前调用getHeight()和getWidth()方法返回的结果都是0.
那么如何在onCreate()阶段得到View的宽度和高度呢?
可以使用View的post()方法。该方法接收一个Runnable线程参数,并将其添加到消息队列中。有趣的是Runnable线程会在UI线程中执行。
方法如下:
view.post(new Runnable() {
@Override
public void run() {
System.out.println("RealWidth=" + view.getWidth());
}
});
官方API:
xml属性 : android:measureWithLargestChild;
设置方法 : setMeasureWithLargestChildEnable(boolean b);
作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸;
默认为false
res/layout/fragment_weight.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/linearlayout" android:tag="linearLayout_parent" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".WeightActivityFragment" tools:showIn="@layout/activity_weight" >
<!--android:measureWithLargestChild作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸; 且只有当父view布局方向上的宽度或高度为wrap_content才有效-->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:text="linearLayout_measureWithLargestChild_true1"/>
<!-- 如果layout_width=“match_parent 则measureWithLargestChild不起作用”-->
<!-- measureWithLargestChild=true 并且子视图总测量宽度>屏幕实际宽度(480x800分辨率)-->
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:measureWithLargestChild="true" android:tag="linearLayout_measureWithLargestChild_true1" >
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button123456789" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0" android:text="A1" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2.0" android:text="A2" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="no0" />
</LinearLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:text="linearLayout_measureWithLargestChild_true2"/>
<!-- measureWithLargestChild=true 子视图总测量宽度<屏幕实际宽度(480x800分辨率)-->
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:measureWithLargestChild="true" android:tag="linearLayout_measureWithLargestChild_true2" >
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button1234" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0" android:text="w1" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2.0" android:text="w2" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="no1" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="@dimen/divider_margin" android:background="#f00"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:text="measureWithLargestChild=false"/>
<!-- measureWithLargestChild=false-->
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:measureWithLargestChild="false" android:tag="linearLayout_measureWithLargestChild_false">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button123456789" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0" android:text="w3" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2.0" android:text="w4" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="no2" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="@dimen/divider_margin" android:background="#f00"/>
<!-- weight使用-->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:tag="linearLayout3" >
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button1"/>
<Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1.0" android:text="Button2" />
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button3" android:layout_weight="1.0" />
<Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2.0" android:text="Button4"/>
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="@dimen/divider_margin" android:background="#f00"/>
<!-- weight配合weightSum使用-->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:tag="linearLayout4" android:gravity="center" android:weightSum="1.0">
<Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.5" android:text="1/2 width" />
</LinearLayout>
<com.antex.weight.LogTextBox android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/box" android:scrollbars="vertical" android:textColor="#f0f"/>
</LinearLayout>
其中包含5个子LinearLayout
第一个LinearLayout 设置measureWithLargestChild=true 并且子视图总测量宽度>屏幕实际宽度(480x800分辨率)
第二个LinearLayout设置measureWithLargestChild=true 并且子视图总测量宽度<屏幕实际宽度(480x800分辨率)
第三个LinearLayout设置measureWithLargestChild=false
第四个LinearLayout主要是weight的使用
第五个LinearLayout是weight配合weightSum使用
最后还有一个自定义LogTextView用来显示各个子视图的测量宽度和布局完成后实际宽度
package com.antex.weight;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
/** * A placeholder fragment containing a simple view. */
public class WeightActivityFragment extends Fragment {
private LogTextBox textView;
public WeightActivityFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_weight, container, false);
textView = (LogTextBox) view.findViewById(R.id.textView1);
LinearLayout layout = (LinearLayout) view.findViewById(R.id.linearlayout);
textView.append("WeightActivityFragment.onCreateView\n");
//调用测量方法, 调用了该方法之后才能通过getMeasuredWidth()等方法获取宽高
layout.measure(0, 0);
traversalView(layout);
return view;
}
@Override
public void onStart() {
super.onStart();
textView.append("\n\nWeightActivityFragment.onStart\n\n");
}
/** *采用递归方法遍历所有view * * @param viewGroup */
public void traversalView(ViewGroup viewGroup) {
//如果是LinearLayout 输出其宽
if(viewGroup instanceof LinearLayout)
doView(viewGroup);
//求当前ViewGroup下子视图的总数量
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
//获取第I个子视图
View view = viewGroup.getChildAt(i);
//如果子视图属于ViewGroup,有可能其下仍然含有子视图,继续判断
if (view instanceof ViewGroup) {
traversalView((ViewGroup) view);
} else {
doView(view);
}
}
}
/** * 处理view * getMeasuredWidth()和getWidth()区别 * getWidth(): View在布局完成后整个View的实际宽度。 * getMeasuredWidth(): 对View上的内容进行测量后得到的View占据的宽度,有可能大于实际宽度 * * * @param view */
private void doView(final View view) {
if(view instanceof Button) {
textView.append(((Button) view).getText().toString().toUpperCase() + " MeasuredWidth=" + view.getMeasuredWidth() + "\n");
view.post(new Runnable() {
@Override
public void run() {
textView.append(((Button) view).getText().toString().toUpperCase() + " RealWidth=" + view.getWidth() + "\n");
}
});
}
else if (view instanceof LinearLayout)
{
textView.append(view.getTag()+" MeasuredWidth="+view.getMeasuredWidth()+"\n");
//利用View的post()方法求出得View的宽度
//如果直接使用getWidth()方法,返回的结果是0
view.post(new Runnable() {
@Override
public void run() {
textView.append(view.getTag()+" RealWidth=" + view.getWidth() + "\n");
}
});
}
}
}
屏幕分辨率: 480x800 mdpi
输出结果为
WeightActivityFragment.onCreateView
linearLayout_parent MeasuredWidth=608
linearLayout_measureWithLargestChild_true1 MeasuredWidth=608
BUTTON123456789 MeasuredWidth=152
A1 MeasuredWidth=152
A2 MeasuredWidth=152
NO0 MeasuredWidth=88
linearLayout_measureWithLargestChild_true2 MeasuredWidth=448
BUTTON1234 MeasuredWidth=112
W1 MeasuredWidth=112
W2 MeasuredWidth=112
NO1 MeasuredWidth=88
linearLayout_measureWithLargestChild_false MeasuredWidth=416
BUTTON123456789 MeasuredWidth=152
W3 MeasuredWidth=88
W4 MeasuredWidth=88
NO2 MeasuredWidth=88
linearLayout3 MeasuredWidth=608
BUTTON1 MeasuredWidth=88
BUTTON2 MeasuredWidth=108
BUTTON3 MeasuredWidth=196
BUTTON4 MeasuredWidth=216
linearLayout4 MeasuredWidth=608
1/2 WIDTH MeasuredWidth=304
WeightActivityFragment.onStart
linearLayout_parent RealWidth=480
linearLayout_measureWithLargestChild_true1 RealWidth=480
BUTTON123456789 RealWidth=152
A1 RealWidth=46
A2 RealWidth=2
NO0 RealWidth=88
linearLayout_measureWithLargestChild_true2 RealWidth=448
BUTTON1234 RealWidth=112
W1 RealWidth=112
W2 RealWidth=112
NO1 RealWidth=88
linearLayout_measureWithLargestChild_false RealWidth=416
BUTTON123456789 RealWidth=152
W3 RealWidth=88
W4 RealWidth=88
NO2 RealWidth=88
linearLayout3 RealWidth=480
BUTTON1 RealWidth=88
BUTTON2 RealWidth=76
BUTTON3 RealWidth=164
BUTTON4 RealWidth=152
linearLayout4 RealWidth=480
1/2 WIDTH RealWidth=240
根据布局及输出数据可以得出如下表格数据
名称 | 权重(或总权重) | layout_width | 测量宽度 | 实际宽度 |
---|---|---|---|---|
LinearLayout_true1 | —— | wrap_content | 608 | 480 |
BUTTON123456789 | 0 | wrap_content | 152 | 152 |
A1 | 1 | wrap_content | 152 | 46 |
A2 | 2 | wrap_content | 152 | 2 |
NO0 | 0 | wrap_content | 88 | 88 |
linearLayout_true2 | —— | wrap_content | 448 | 448 |
BUTTON1234 | 0 | wrap_content | 112 | 112 |
W1 | 1 | wrap_content | 112 | 112 |
W2 | 2 | wrap_content | 112 | 112 |
NO1 | 0 | wrap_content | 88 | 88 |
linearLayout_false | —— | wrap_content | 416 | 416 |
BUTTON123456789 | 0 | wrap_content | 152 | 152 |
W3 | 1 | wrap_content | 88 | 88 |
W4 | 2 | wrap_content | 88 | 88 |
NO2 | 0 | wrap_content | 88 | 88 |
linearLayout3 | —— | match_parent | 608 | 480 |
BUTTON1 | 0 | wrap_content | 88 | 88 |
BUTTON2 | 1.0 | 0 | 108 | 76 |
BUTTON3 | 1.0 | wrap_content | 196 | 164 |
BUTTON4 | 2.0 | 0 | 216 | 152 |
linearLayout4 | 1 | match_parent | 608 | 480 |
1/2 WIDTH | 0.5 | 0 | 304 | 240 |
结论:
1. 测量过程发生在onCreateView()阶段,在onStart()方法之后,视图才能得到自身的实际高和宽
2. 如果layout_width 不是“wrap_content ”则measureWithLargestChild不起作用(这个没在此表格中体现出来,读者可以自己测试)
3. 当measureWithLargestChild=true 并且子视图总测量宽度>屏幕实际宽度时,所有带权重(weight)的子元素都会具有最大子元素的测量宽度,但带权重的子元素最后实际宽度却不是,会出现布局异常;并且LinearLayout的实际宽度=屏幕最大宽度(这里是480)
4. 当measureWithLargestChild=true 并且子视图总测量宽度<屏幕实际宽度时,所有带权重(weight)的子元素都会具有最大子元素的测量宽度和实际宽度;并且LinearLayout的实际宽度=最大子元素的宽度*子元素个数(这里是112*4=448)
5. 当measureWithLargestChild=false时,不受以上约束
6. 当父layout_width =“wrap_content “时,weight属性不起作用(由linearLayout_false 得出此结论)
7. Button宽度计算公式:
原始宽度+权重*父视图剩余空间/权重和
7.1 未指定android:weightSum属性时,权重和=所有子控件的weight之和,weight未指定时为0
7.2如果指定了android:weightSum属性,权重和=android:weightSum指定的值。不管子控件weight和是多少
7.3weight是对剩余空间的分配而不是对LinearLayout空间的分配
我们用上面表格中的数据来验证下: linearLayout3 中Button1和Button3原始宽度为wrap_content可得知 原始宽度为88,Button2和Button4原始宽度为0 Button1 weidth=88=88+0*(480-88-88)/(0+1.0+1.0+2.0) Button2 weidth=76=0+1.0*(480-88-88)/(0+1.0+1.0+2.0) Button3 weidth=164=88+1.0*(480-88-88)/(0+1.0+1.0+2.0) Button4 weidth=152=0+2.0*(480-88-88)/(0+1.0+1.0+2.0) linearLayout4中 1/2 WIDTH weidth=240=0+0.5*480/1.0
开发工具:Android Studio1.4
SDK: Android 6.0
API 23
代码下载:Weight.zip