安卓開發學習之005 LinearLayout之Weight/measureWithLargestChild詳解
本文主要介紹LinearLayout中分隔線Weight的使用方法
涉及到以下幾點內容:
- 佈局繪製過程
- 遍歷檢視
- 在onCreate()方法中獲取View的寬度和高度
- android:measureWithLargestChild使用說明
- weight及weightSum使用說明
1.佈局繪製過程
在講解measureWithLargestChild和weight使用方法之前必須先來簡單瞭解下佈局的繪製過程。
Android開發文件中有如下描述:
“繪製佈局由兩個遍歷過程組成:測量過程和佈局過程。測量過程由measure(int,int)方法完成,該方法從上到下遍歷檢視樹。在遞迴遍歷過程中,每個檢視都會向下層傳遞尺寸和規格。當measure方法遍歷結束時,每個檢視都儲存了各自的尺寸資訊。第二個過程由layout(int,int,int,int)方法完成,該方法也是由上而下遍歷檢視樹,在遍歷過程中,每個父檢視通過測量過程的結果定位所有子檢視的位置資訊。”
2.如何遍歷檢視
/**
*採用遞迴方法遍歷所有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
}
3.在onCreate()方法中獲取View的寬度和高度
這裡有兩個概念:
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());
}
});
4.measureWithLargestChild和weight使用說明
官方API:
xml屬性 : android:measureWithLargestChild;
設定方法 : setMeasureWithLargestChildEnable(boolean b);
作用 : 該屬性為true的時候, 所有帶權重的子元素都會具有最大子元素的最小尺寸;
預設為false
4.1先看下佈局檔案
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用來顯示各個子檢視的測量寬度和佈局完成後實際寬度
4.2java程式碼
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");
}
});
}
}
}
4.3上執行後結果圖
4.4最後文字輸出內容如下
螢幕解析度: 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
4.5輸出結果分析
根據佈局及輸出資料可以得出如下表格資料
名稱 | 權重(或總權重) | 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