ScrollView嵌套使用ListView沖突的解決與分析


  因為ScrollView與ListView都是具有滾動條的控件,所以嵌套在一起使用的時候可能會出現事件的沖突,比如我就遇見了ListView中只顯示一條數據的問題。解決的辦法,就是自定義了一個ListView,重寫它的onMeasure()方法:

  

 1 public class MyListView extends ListView {
 2 
 3     public MyListView(Context context) {
 4         super(context);
 5         // TODO Auto-generated constructor stub
 6     }
 7 
 8     public MyListView(Context context, AttributeSet attrs) {
 9         super(context, attrs);
10         // TODO Auto-generated constructor stub
11     }
12 
13     public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
14         super(context, attrs, defStyleAttr);
15         // TODO Auto-generated constructor stub
16     }
17 
18     @Override
19     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
20         int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
21         super.onMeasure(widthMeasureSpec, expandSpec);
22 
23     }
24 
25 }

MeasureSpec.makeMeasureSpec()方法是由我們給出的尺寸大小和模式,來生成一個包含這兩個信息的int類型的變量。根據我們提供打大小值和模式創建一個測量值(格式)。

每次onMeasure()的時候都重新繪制高度,這樣就不會只顯示一條數據。

為什么會只顯示一行的高度呢?這其實跟ListView的源碼有關,ListView的父容器測量模式為UNSPECIFIED的時候,ListView的高度默認為一個item的高度

ScrollView中的部分源碼:

1 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
2         if (heightMode == MeasureSpec.UNSPECIFIED) {
3             return;
4         }

ListView中源碼如下:

1 if (heightMode == MeasureSpec.UNSPECIFIED) {
2     heightSize = mListPadding.top + mListPadding.bottom + childHeight +
3                    getVerticalFadingEdgeLength() * 2;
4 }

 

原理分析:

  原理的理解是看了這篇博主的博文才理解的,寫的很清楚,不明白的可以去看下:

  ScrollView嵌套ListView,ListView完全展開及makeMeasureSpec測量機制原理分析

  從上面看出,我們把高度寫成了一個固定的值expandSpec,這個值得計算方法如下:

1 expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);

  可以看出我們將整數類型的最大值向右移了兩位作為參數傳入,模式我們傳入了常量MeasureSpec.AT_MOST。而為什么我們重新計算了高度值,並且還是一個這么奇葩的數值作為參數傳入呢?這就跟Android中的實體測量機制相關了,android中規定,測量的值,高度或者是寬度,是作為一個int類型的數值,但不是普通的int,而是一個經過處理的int值,在view視圖中,我們制定一個高度或者寬度,需要指定兩個參數,一個是具體大小,另一個是測量模式,測量模式就是我們在布局中經常用到的MATCH_PARENT、WRAP_CONTENT。他們是一個int類型的常量,分別對應值:

1 LayoutParams.MATCH_PARENT  對應 MeasureSpec.EXACTLY
2 LayoutParams.WRAP_CONTENT  對應 MeasureSpec.AT_MOST

在源碼中的展示為:

 1 public class View implements ... {
 2 ...
 3 public static class MeasureSpec {
 4 private static final int MODE_SHIFT = 30; //移位位數為30
 5 //int類型占32位,向左移位30位,用來與size和mode進行"&"運算,獲取對應值。
 6 private static final int MODE_MASK = 0x3 << MODE_SHIFT;
 7  //向左移位30位,其值為00 + (30位0)  , 即 0x0000(16進制表示)  
 8     public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
 9     //向左移位30位,其值為01 + (30位0)  , 即0x1000(16進制表示)  
10     public static final int EXACTLY     = 1 << MODE_SHIFT;  
11     //向左移位30位,其值為02 + (30位0)  , 即0x2000(16進制表示)  
12     public static final int AT_MOST     = 2 << MODE_SHIFT;  
13 
14     //創建一個整形值,其高兩位代表mode類型,其余30位代表長或寬的實際值。可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size  
15     public static int makeMeasureSpec(int size, int mode) {  
16         return size + mode;  
17     }  
18     //獲取模式  ,與運算  
19     public static int getMode(int measureSpec) {  
20         return (measureSpec & MODE_MASK);  
21     }  
22     //獲取長或寬的實際值 ,與運算  
23     public static int getSize(int measureSpec) {  
24         return (measureSpec & ~MODE_MASK);  
25     }  
26 
27 }  
28 ...

可以看到,EXACTLY和AT_MOST的值是:

1 private static final int MODE_SHIFT = 30;      
2 
3         public static final int EXACTLY     = 1 << MODE_SHIFT;    //填滿父控件高度
4         public static final int AT_MOST     = 2 << MODE_SHIFT;    //自適應當前控件高度

可以看到,分別是將1和2向左移了30位,為什么向左移30位呢?

android中把測量出的int做了處理,int類型的長度是32位,把前兩位作為標記位,標記了測量模式,如EXACTLY、AT_MOST,把最后30位作為測量的具體高度或者是寬度

也就是說把一個int分為了兩部分,是一個int同時具有了測量模式和具體數值兩部分信息!

EXACTLY的值是1向左進位30,就是01 00000000000…(01后跟30個0) 
AT_MOST的值是2向左進位30,就是10 00000000000…(10后跟30個0) 

所以我們在調用MeasureSpec.makeMeasureSpec(size,mode)方法時,傳入的size參數要把Integer.MAX_VALUE右移2位,因為前兩位會被認為是標志,而不是值。這樣我們傳入的參數才會被認為是最大的int類型的值,同時傳入AT_MOST作為模式,那么前兩位就會被賦值為10,那么我們來實際計算一下,我們調用MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST)的實際結果是多少:

int類型長度是32位,其中包含1個標准位,所有最大值Integer.MAX_VALUE為 2的31次方 :2147483648 
16進制下為0x7fffffff,二進制為:01 11111111111…(01后跟30個1) 
由於前兩位在android測量基礎下是無效的標志位,所以我們右移2位的結果為000111111111111… 
調用makeMeasureSpec方法后會把前兩位替換為AT_MOST的前兩位(其實是直接相加) 
結果為:100111111111… 十六進制:9fffffff,十進制:-1610612737

所以你調試的時候后發現,返回的值為-1610612737 ,就是這個原因,因為前2位是無效的標志位,所以其實這個數所代表的最大值是 2的29次方:536870912。所以對於android測量機制來理解我們傳入的-1610612737,是這樣理解的:
1 -1610612737  代表: 測量模式為AT_MOST,最大高度為536870912(2的29次方)

 關於MeasureSpec類的使用可以移文這篇博客:

android學習——MeasureSpec介紹及使用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM