1. 程式人生 > >ScrollView巢狀ListView或GridView等,使得其高度自適應解決方案

ScrollView巢狀ListView或GridView等,使得其高度自適應解決方案

這類的文章有很多,寫此文的目的是為了備忘吧。ScrollView裡面巢狀ListView或GridView等,兩個View都有滾動的效果,在巢狀使用時起了衝突,一般不建議兩者套用。解決的方案有很多但是最優的解決方案如下:
package com.base.frame.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

public class MyListView extends ListView {

	public MyListView(Context context) {
		super(context);
	}

	public MyListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

    /**
     * 重寫該方法,達到使ListView適應ScrollView的效果(因為listview的高度是不確定的,所以每次要重新測量)
     */
	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
        MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
}

原理解析:

瞭解自定義view 的肯定知道 onMeasure()是什麼意思,makeMeasureSpec()方法中 Integer.MAX_VALUE >> 2
在Android中,一個控制元件所佔的模式和大小是通過一個整數int來表示的,這裡很多同學就疑惑了,一個int值是怎麼來表示模式的大小的,這裡來看一張圖片:


原來,Android裡面把int的最高2兩位來表示模式,最低30位來表示大小。
測量View大小使用的是onMeasure函式,我們可以從onMeasure的兩個引數中取出寬高的相關資料:
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthsize = MeasureSpec.getSize(widthMeasureSpec);      //取出寬度的確切數值
        int widthmode = MeasureSpec.getMode(widthMeasureSpec);      //取出寬度的測量模式
        int heightsize = MeasureSpec.getSize(heightMeasureSpec);    //取出高度的確切數值
        int heightmode = MeasureSpec.getMode(heightMeasureSpec);    //取出高度的測量模式
    }

從上面可以看出 onMeasure 函式中有 widthMeasureSpec 和 heightMeasureSpec 這兩個 int 型別的引數, 毫無疑問他們是和寬高相關的, 但它們其實不是寬和高, 而是由寬、高和各自方向上對應的測量模式來合成的一個值:
測量模式一共有三種, 被定義在 Android 中的 View 類的一個內部類View.MeasureSpec中:
3種模式
1):UNSPECIFIED模式,官方意思是:父佈局沒有給子佈局強加任何約束,子佈局想要多大就要多大,說白了就是不確定大小
2)EXACTLY模式,官方意思是:父佈局給子佈局限定了準確的大小,子佈局的大小就是精確的,父親給多大就是多大
3)AT_MOST模式,官方意思是:父佈局給定了一個最大的值,子佈局的大小不能超過這個值,當然可以比這個值小
private static final int MODE_SHIFT = 30;public static final int UNSPECIFIED = 0 << MODE_SHIFT;public static final int EXACTLY = 1 << MODE_SHIFT;public static final int AT_MOST = 2 << MODE_SHIFT;

不確定模式是0左移30位,也就是int型別的最高兩位是00
精確模式是1左移30位,也就是int型別的最高兩位是01
最大模式是是2左移30位,也就是int型別的最高兩位是10

所以呼叫了makeMeasureSpec方法,這個方法是用來生成一個帶有模式和大小資訊的int值的,第一個引數Integer.MAX_VALUE >> 2,這個引數是傳的一個大小值,為什麼是這個值呢,我們現在已經知道了,我們要生成的控制元件,它的大小最大值是int的最低30位的最大值,我們先取Integer.MAX_VALUE來獲取int值的最大值,然後左移2位就得到這個臨界值最大值了
當然,我們在手機上的控制元件的大小不可能那麼大,極限值就那麼大,實際肯定比那個小,所以這個模式就得選擇MeasureSpec.AT_MOST了,最後將生成的這個大小傳遞給父控制元件就可以了,super.onMeasure(widthMeasureSpec, expandSpec),這個函式只改變的是控制元件的高度,寬度沒有改變,實際開發當中不管listview有多少條資料,都能一次性展現出來。最後選用第三種方式完美實現。