文章最早釋出於我的微信公眾號 Android_De_Home 中,歡迎大家掃描下面二維碼關注微信公眾獲取更多知識內容。
本文為sydMobile原創文章,可以隨意轉載,但請務必註明出處!
ScrollView巢狀ListView會出現的問題,相信大家已經見到的非常多了,對於解決方法也是瞭如指掌了。但是原理你清楚了嗎?這裡主要講為什麼會出現這種問題,已經解決這個問題的原理。
ScrollView巢狀ListView出現的問題
ScrollView巢狀ListView會出現的問題,相信大家都已經見的非常多了,對於怎麼解決也不陌生了。
這裡再來說一下:
出現的問題:
ListView會顯示不全
解決方法:
最常見的一種方法:
自己繼承ListView,重寫onMeasure()方法
@Override
public void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
super.onMeasure(widthMeasureSpec,MeasureSpec.makeMeasureSpec
(Integer.MAX_VALUe)>>2,MeasureSpec.AT_MOST));
}
複製程式碼
一般的我們僅僅需要這樣重寫這個方法就可以很順利的解決ScrollView巢狀ListView出現的ListView顯示不全的問題了。那麼是因為什麼呢?下面我們就從原理上說說!
解決原理
說起原理就可MeasureSpec類分不開了,先來介紹一下這個類。
MeasureSpec類是View的一個內部靜態類,MeasureSpec類封裝了從父佈局到子佈局傳遞的佈局需求。每個MeasureSpec物件代表了寬度和高度的要求。一個MeasureSpec類的表示由控制元件大小和模式兩組成。有三種模式:
- UNSPECIFIED
父佈局沒有對子佈局施加任何的限制,子佈局可以是任何他想要的大小 - EXACTLY
父佈局已經確定了子佈局的大小,子佈局會在父佈局給出的界限內。子佈局的大小是精確的。父佈局給多大就是多大。 - AT_MOST
父佈局會給定一個最大的值,子佈局的大小是不能超過這個值的。但是可以比這個值小。
MeasureSpec類為了減少物件的分配用了一個整數來實現這個功能(父佈局傳遞到子佈局的的佈局需求),這個整數是用模式和大小來表示。
那麼這個整數是怎麼來實現這個功能的呢?
我們都知道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這個方法了。
這個方法就是用給定的大小和模式建立一個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"就可以了。