1. 程式人生 > >安卓開發學習之005 LinearLayout之Weight/measureWithLargestChild詳解

安卓開發學習之005 LinearLayout之Weight/measureWithLargestChild詳解

本文主要介紹LinearLayout中分隔線Weight的使用方法
涉及到以下幾點內容:

  1. 佈局繪製過程
  2. 遍歷檢視
  3. 在onCreate()方法中獲取View的寬度和高度
  4. android:measureWithLargestChild使用說明
  5. 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