文章最先發佈於個人微信公衆號 Android_De_Home 中,歡迎你們掃描下面二維碼關注微信公衆獲取更多知識內容。
本文爲sydMobile原創文章,能夠隨意轉載,但請務必註明出處!android
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類是View的一個內部靜態類,MeasureSpec類封裝了從父佈局到子佈局傳遞的佈局需求。每一個MeasureSpec對象表明了寬度和高度的要求。一個MeasureSpec類的表示由控件大小和模式兩組成。有三種模式:對象
MeasureSpec類爲了減小對象的分配用了一個整數來實現這個功能(父佈局傳遞到子佈局的的佈局需求),這個整數是用模式和大小來表示。blog
那麼這個整數是怎麼來實現這個功能的呢?
咱們都知道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
在這個MeasureSpec類中最重要的一個方法恐怕就是makeMeasureSpec這個方法了。
這個方法就是用給定的大小和模式建立一個int類型的數來知足父佈局到子佈局傳遞的佈局需求。第一個參數 size就是父佈局給子佈局傳遞的大小,第二個參數是模式(就是在上面的三個模式中選擇一個)。好了,到這裏makeMeasureSpec()這個方法也講了。
方法一:
上面已經講了,重寫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"就能夠了。