從原理上說說ScrollView嵌套ListView的問題

文章最先發佈於個人微信公衆號 Android_De_Home 中,歡迎你們掃描下面二維碼關注微信公衆獲取更多知識內容。
本文爲sydMobile原創文章,能夠隨意轉載,但請務必註明出處!android

ScrollView嵌套ListView會出現的問題,相信你們已經見到的很是多了,對於解決方法也是瞭如指掌了。可是原理你清楚了嗎?這裏主要講爲何會出現這種問題,已經解決這個問題的原理。微信

ScrollView嵌套ListView出現的問題

ScrollView嵌套ListView會出現的問題,相信你們都已經見的很是多了,對於怎麼解決也不陌生了。ide

這裏再來講一下:
出現的問題:
ListView會顯示不全佈局

解決方法:
最多見的一種方法:spa

本身繼承ListView,重寫onMeasure()方法3d

@Override
	public void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
		super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec
		(Integer.MAX_VALUe)>>2,MeasureSpec.AT_MOST));

	}
複製代碼

通常的咱們僅僅須要這樣重寫這個方法就能夠很順利的解決ScrollView嵌套ListView出現的ListView顯示不全的問題了。那麼是由於什麼呢?下面咱們就從原理上說說!code

解決原理

提及原理就可MeasureSpec類分不開了,先來介紹一下這個類。
cdn

MeasureSpec類

MeasureSpec類是View的一個內部靜態類,MeasureSpec類封裝了從父佈局到子佈局傳遞的佈局需求。每一個MeasureSpec對象表明了寬度和高度的要求。一個MeasureSpec類的表示由控件大小和模式兩組成。有三種模式:對象

  • UNSPECIFIED
    父佈局沒有對子佈局施加任何的限制,子佈局能夠是任何他想要的大小
  • EXACTLY
    父佈局已經肯定了子佈局的大小,子佈局會在父佈局給出的界限內。子佈局的大小是精確的。父佈局給多大就是多大。
  • AT_MOST
    父佈局會給定一個最大的值,子佈局的大小是不能超過這個值的。可是能夠比這個值小。

MeasureSpec類爲了減小對象的分配用了一個整數來實現這個功能(父佈局傳遞到子佈局的的佈局需求),這個整數是用模式和大小來表示。blog

那麼這個整數是怎麼來實現這個功能的呢?

int表示形式

咱們都知道int類型的是32位,那麼表示形式就是,向上面圖中的那樣,前兩位表明了模式(就是前面提到的那三種),後30位表明了組件的大小。這樣就用整數形式來表示模式和大小了。

具體的看一下三種模式

UNSPECIFIED 模式
UNSPECIFIED == 0 << MODE_SHIFT 也就是 0 向左位移30位,結果就是int類型的最高位是 00

EXACTLY 模式
EXACTLY == 1 << MODE_SHIFT ;也就是 01向左位移30位,結果就是int類型的最高兩位是 01

AT_MOST 模式 AT_MOST = 2 << MODE_SHIFT ;也就是 10 向左位移30位,結果就是int類型的最高兩位是 10

makeMeasureSpec()方法

在這個MeasureSpec類中最重要的一個方法恐怕就是makeMeasureSpec這個方法了。

makeMeasureSpec

這個方法就是用給定的大小和模式建立一個int類型的數來知足父佈局到子佈局傳遞的佈局需求。第一個參數 size就是父佈局給子佈局傳遞的大小,第二個參數是模式(就是在上面的三個模式中選擇一個)。好了,到這裏makeMeasureSpec()這個方法也講了。

ScrollView嵌套ListView解決方法

方法一:

上面已經講了,重寫ListView的onMeasure()方法

@Override
	protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
			super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec(Integer

			.MAX_VALUE >> 2,MeasureSpec.AT_MOST));
	}
	
複製代碼

咱們在修改的時候並無改變widthMeasureSpec,僅僅是修改了heightMeasureSpec,由於ScrollView設置成了上下滑動,橫向並無滑動,全部在橫向上並無和ListView產生衝突。因此傳入的widthMeasureSpec是正確的,而heightMeasureSpec是不正確的,由於ListView嵌套在ScrollView中,也就是說ScrollView是ListView的子佈局,這個時候他們的滑動事件發生了衝突,這個值也就不正確了,不是LIstView的實際高度。因此咱們要重寫傳入height,第一個參數爲何是Integer.MAX_VALUE >>2 呢 ?咱們說了MeasureSpec用 int類型表示前兩位表明模式,後30位表明大小,咱們就須要讓後面30位是int類型中最大的值就能夠了。爲何選擇AT_MOST模式呢?這個模式是父佈局給定一個值,不能超過這個值,咱們很顯然已經給了最大值了。

方法二:

既然測不出高度,那麼我就手中在代碼中設置ListView的高度。

public static void setListViewHeightBasedOnChildren(ListView listView) {
	    if(listView == null) return;

	    ListAdapter listAdapter = listView.getAdapter();
	    if (listAdapter == null) {
	        // pre-condition
	        return;
	    }

	    int totalHeight = 0;
	    for (int i = 0; i < listAdapter.getCount(); i++) {
	        View listItem = listAdapter.getView(i, null, listView);
	        listItem.measure(0, 0);
	        totalHeight += listItem.getMeasuredHeight();
	    }

	    ViewGroup.LayoutParams params = listView.getLayoutParams();
	    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
	    listView.setLayoutParams(params);
}
複製代碼

這種方法有前提條件限制:
Adapter中getView方法返回的View的必須由LinearLayout組成,由於只有LinearLayout纔有measure()方法,若是使用其餘的佈局如RelativeLayout,在調用listItem.measure(0, 0);時就會拋異常,由於除LinearLayout外的其餘佈局的這個方法就是直接拋異常!

總結 自定義ListView比較好用,還有一個問題就是不管使用上面哪個方法,當你的ListView在加載數據的時候,若是當前頁面沒展現徹底,那麼scrollView會自動往下滑動一點,也就是形成了你進入這個頁面的時候,默認頁面是往下滑動了一下,而不是在最頂端。 形成這個問題主要的緣由仍是焦點問題,ListView默認狀況下,isFocusableInTouchMode和isFocusable都是false的,可是當在加載數據後這兩個值就會變爲true了。若是在佈局中沒有其餘view獲取焦點,這個時候ListView就爭奪到了焦點,也就形成了滑動。

這個問題的解決方法:
1.在加載完數據後設置ScrollView滑動到頂部scrollView.smoothScrollTo(0,0)
這種作法是有缺點的,你會看到屏幕滑動一下。

2.使用descendantFousability屬性

descendantFocusability有三種屬性
beforeDescendant:viewgroup會優先其子類控件而獲取到焦點。
afterDescendant:viewgroup只有當其子類控件不須要獲取焦點的時候才獲取焦點。
blocksDescendants: viewgroup會覆蓋子類控件而直接得到焦點

在ScrollView的LinearLayout中添加android:denscendantFocusability = "blocksDescendants"就能夠了。

歡迎你們關注個人微信公衆號,和我交流分享
相關文章
相關標籤/搜索