在工作中,我多次遇到過ScrollView視圖嵌套ListView的問題。有許多在線解決方案,但它們很雜且不完整。我試過很多方法,每種方法都有自己的優缺點。
在這裏,我將從使用ScrollView嵌套ListView結構的原因、該結構遇到的問題、幾種解決方案以及優缺點比較四個方面進行闡述、分析和總結。
事實上,不僅是ListView,從AbsListView繼承的其他類也是如此,包括ExpandableListView、GridView等。為方便解釋,以下均以ListView表示。
首先,為什麽使用ScrollView來嵌套ListView的奇怪結構?
ScrollView和ListView都是滾動結構。從理論上講,UI上這兩個控件的功能是相同的,但是請看下面的設計:
這是天貓商城的確認頁面。ExpandableListView嵌套在ScrollView中,上面有壹些固定控件,下面也有壹些固定控件,所以整體上應該是可滾動的。列表數據應該嵌入固定數據中,並作為壹個整體滾動在壹起。有了這樣的設計需求,就有了ScrollView嵌套ListView的奇怪結構。
其次,在ScrollView和ListView的嵌套結構中遇到的問題
話不多說,只看失敗例子:
& lt滾動視圖
Android:id =“@+id/act _ solution _ 1 _ SV“
Android:layout _ width =“fill _ parent“
Android:layout _ height =“fill _ parent“& gt;
& lt線性布局
Android:layout _ width =“fill _ parent“
Android:layout _ height =“wrap _ content“
Android:orientation =“vertical“& gt;
& lt文本視圖
Android:layout _ width =“fill _ parent“
Android:layout _ height =“wrap _ content“
Android:text =“\ n列表查看以上數據“/& gt;
& lt列表視圖
Android:id =“@+id/act _ solution _ 1 _ LV“
Android:layout _ width =“fill _ parent“
Android:layout _ height =“wrap _ content“& gt;
& lt/ListView>
& lt文本視圖
Android:layout _ width =“fill _ parent“
Android:layout _ height =“wrap _ content“
Android:text =“\ n下面的列表視圖數據\ n“/& gt;
& lt/linear layout & gt;
& lt/scroll view & gt;
ScrollView中只能放置壹個控件,通常放置LinearLayout,orientation屬性的值為vertical。將需要呈現的內容放到LinearLayout中。ListView也在其中,ListView的高度被設置為適合自己的內容(wrap_content)。乍壹看,應該沒有問題。但是看看下面的實際效果圖:
圖中的黑框是ListView,包含20條數據,但只顯示了1條。
控件的屬性設置沒有問題,但是為什麽我沒有按照我的想法去做呢?
請看下圖:
是不是有點清楚了?原因是滾動事件的消耗處理和ListView控件的高度設置。
雖然我讀過很多源代碼,但我不知道如何開始談論它。我大概知道原因,但不知道怎麽徹底理清。求高手指點…
第三,解決問題的方法
1.手動設置ListView的高度。
經過測試發現,直接在xml中指定ListView的高度可以解決這個問題,但ListView中的數據是可變的,需要測量實際高度。於是通過手動代碼設置ListView高度的方法誕生了。
/**
*動態設置ListView的高度。
* @param列表視圖
*/
公共靜態void setListViewHeightBasedOnChildren(ListView ListView ){
if(listView = = null)返回;
list adapter list adapter = listview . get adapter();
if(list adapter = = null ){
//前置條件
返回;
}
int total height = 0;
for(int I = 0;我& ltlist adapter . get count();i++) {
view listItem = list adapter . getview(I,null,listView);
listitem . measure(0,0);
total height+= listitem . getmeasuredheight();
}
視圖組。layout params params = listview . getlayout params();
params . height = total height+(listview . getdividerheight()*(list adapter . get count()-1);
listview . setlayoutparams(params);
}
上面的方法是設置ListView的高度。為ListView設置適配器後,它可以解決該問題。
但是這種方法有兩個細節需要註意:
首先,適配器中getView方法返回的視圖必須由LinearLayout組成,因為只有LinearLayout具有measure()方法。如果使用其他布局,如RelativeLayout,則調用listitem . measure(0,0);妳會拋出異常,因為除了LinearLayout之外的這種布局方法就是直接拋出異常,沒有任何理由。我最初使用了這種方法,但由於子控件的頂部布局是RelativeLayout,我不斷報告錯誤,不得不放棄這種方法。
其次,您需要手動將ScrollView滾動到頂部,因為如果使用此方法,默認情況下ScrollView頂部的項目是ListView。我不知道具體原因,所以我需要向上帝尋求答案...您可以在活動中設置它:
SV =(scroll view)findViewById(r . id . act _ solution _ 1 _ SV);
2.使用單個ListView替換ScrollView中的所有內容。
這個方法是我自己在嘗試了幾種方法都失敗的情況下想出來的。
用壹張圖來解釋這種方法的思想:
也就是說,所有需要放在ScrollView中的內容都放在ListView中,原始ListView上下的數據都被視為當前ListView的壹個itemView,相當於原始ListView中的單個數據。
xml布局非常簡單:
& lt列表視圖
Android:id =“@+id/act _ solution _ 2 _ LV“
Android:layout _ width =“fill _ parent“
Android:layout _ height =“wrap _ content“& gt;
& lt/ListView>
單獨的ListView就可以了。
原始ListView上方和下方的數據被寫入兩個xml布局文件:
就Java代碼而言,您需要定制壹個適配器,判斷適配器中getView方法中的位置值,並根據位置值決定要插值的布局:
公共視圖getView(int position,View convertView,View group parent ){
//列表中的第壹項
if(position = = 0 ){
convert view = inflater . inflate(r . layout . item _ solution 2 _ top,null);
返回convertView
}
//列表中的最後壹項
else if(position = = 21 ){
convert view = inflater . inflate(r . layout . item _ solution 2 _ bottom,null);
返回convertView
}
//常用列表項
ViewHolder h = null
if(convert view = = null | | convert view . gettag()= = null ){
convert view = inflater . inflate(r . layout . item _ listview _ data,null);
h =新視圖持有者();
h . TV =(TextView)convert view . findviewbyid(r . id . item _ listview _ data _ TV);
convert view . settag(h);
}否則{
h =(view holder)convert view . gettag();
}
H.tv.setText(“文章“+位置+“數據“);
返回convertView
}
在Activty中,您只需要直接為ListView設置壹個自定義適配器。
LV =(ListView)findViewById(r . id . act _ solution _ 2 _ LV);
adapter = new adapterforlistview 2(this);
lv.setAdapter(適配器);
3.用LinearLayout替換ListView。
既然ListView不能適應ScrollView,我們為什麽要更改壹個可以適應ScrollView的控件呢?LinearLayout是最佳選擇。但是如果我仍然想使用已定義的適配器呢?我們只需要自定義壹個從LinearLayout繼承的類,並添加壹個對BaseAdapter的適配。
導入Android . content . context;
導入Android . util . attributeset;
導入Android . util . log;
導入Android . view . view;
導入Android . widget . base adapter;
導入Android . widget . linear layout;
/**
*替換ListView的LinearLayout,以便它可以成功嵌套在ScrollView中。
* @ authortry _ dragon
*/
公共類LinearLayoutForListView擴展了LinearLayout {
專用BaseAdapter適配器;
private onclick listener onclick listener = null;
/**
*裝訂布局
*/
public void bindlinelayout(){
int count = adapter . get count();
this . remove allviews();
for(int I = 0;我& lt數數;i++) {
view v = adapter . get view(I,null,null);
v . setonclicklistener(this . onclicklistener);
add view(v,I);
}
log . v(“count tag“,““+count);
}
公共LinearLayoutForListView(上下文Context ){
super(上下文);
將上面的代碼副本保存為LinearLayoutForListView.class,或者直接在您自己的項目中的Demo中復制此類。我們只需要用這個類替換原始xml布局文件中的ListView:
& ltpm . nestification between scrollviewandbslistview . my widgets . linearrayoutforlistview
Android:id =“@+id/act _ solution _ 3 _ mylinearlayout“
Android:layout _ width =“fill _ parent“
Android:layout _ height =“wrap _ content“
Android:orientation =“vertical“& gt;
& lt/pm . nestification between scrollviewandbslistview . my widgets . linearrayoutforlistview & gt;
在活動中,ListView也更改為LinearLayoutForListView,並且可以成功運行。
mylinearlayout =(linearrayoutforlistview)findViewById(r . id . act _ solution _ 3 _ mylinearlayout);
adapter = new AdapterForListView(this);
mylinearlayout.setAdapter(適配器);
4.自定義可適應滾動視圖的列表視圖。
這種方法與上述方法類似。方法3是自定義LinearLayout來代替ListView的功能,但是如果我的脾氣比較倔,想用ListView怎麽辦?然後妳必須自定義壹個類來繼承ListView,並重寫其onMeasure方法以達到適應ScrollView的效果。
以下是繼承ListView的自定義類:
導入Android . content . context;
導入Android . util . attributeset;
導入Android . widget . listview;
公共類ListViewForScrollView擴展ListView {
公共ListViewForScrollView(上下文Context ){
super(上下文);
}
公共ListViewForScrollView(上下文Context,AttributeSet attrs ){
super(上下文,attrs);
}
公共ListViewForScrollView(上下文上下文,屬性集屬性,
int def style ){
super(context、attrs、def style);
}
@覆蓋
/**
*重寫此方法以達到使ListView適應ScrollView的效果。
*/
受保護的void on measure(int width measurespec,int height measurespec ){
int expand spec = measure spec . make measure spec(整數。MAX _ VALUE & gt& gt2,
測量規格。至多);
super . on measure(widthMeasureSpec、expandSpec);
}
}
三種構造方法根本不需要動,只要重寫onMeasure方法,需要改動的地方不比方法3少壹點點…
只需將xml布局和活動中使用的ListView更改為該自定義ListView。保存代碼...
此方法和方法1有相同的問題,即默認顯示的第壹項是ListView,需要手動將ScrollView滾動到頂部。
SV =(scroll view)findViewById(r . id . act _ solution _ 4 _ SV);
SV . smooth scroll to(0,0);
5.設置ScrollView的屬性,以便ListView可以成功嵌套(無法達到預期效果)。
我在寫演示的時候發現了這個方法。我的第壹反應是為什麽我用這種方法編寫這個演示,只需在布局文件中添加壹個屬性。然而,結果是ListView的大小填滿了ScrollView的其余部分,但它無法滾動。這是壹個致命的問題…
廢話少說。在布局文件中:
& lt滾動視圖
Android:id =“@+id/act _ solution _ 5 _ SV“
Android:layout _ width =“fill _ parent“
Android:layout _ height =“fill _ parent“
Android:layout _ below =“@+id/act _ solution _ 5 _ VG _ top“
Android:fill viewport =“true“& gt;
只需將fillViewport的屬性設置為true。簡單吧?
但是不知道怎麽解決無法滾動的致命問題,繼續求大神解答…
第四,比較了幾種方法的優缺點
上面* * *給出了4中可用的親本測試方法,每種方法都有其自身的條件和復雜性。
我從幾個方面來分析幾種方法的優缺點。
方法1的優點是不需要對使用的控件進行任何更改,只需要使用壹個現成的方法即可,最大的限制是ListView的item只能由LinearLayout組成,這對於壹些復雜的布局並不適用。如果您的項目迫切需要解決這個問題,並且它滿足使用該方法的條件,即ListView的項布局很簡單,完全由LinearLayout組成,則您只需要采用setListViewHeightBased子方法。
方法2的優點是布局文件設計簡單,並且活動中的代碼很少。但是缺點是自定義適配器變得非常復雜,並且執行效率會很低,因為findViewById是壹個非常耗時的操作,而使用ViewHolder結構可以解決耗時問題(感興趣的童鞋可以搜索壹個ViewHolder結構)。然而,使用方法2會破壞這種結構。如果妳的工程設計比較簡單,ListView中的項目相對較少,ListView上下的數據很少,項目之間的交互也很少,妳可以嘗試壹下。
方法3的優點是它完全解決了在ScrollView中嵌套ListView的問題,同時代碼較少。妳甚至可以直接使用LinearLayout,並在Activity中為LinearLayout手動添加子控件。但是需要註意的是,在添加它之前需要調用它的removeAllViews方法,否則可能會發生意想不到的事情,然後就會錯過天堂中的ListView。缺點不明顯,但仍然有兩個:第壹,您無法在xml layout的圖形布局視圖中直接看到效果,因為您沒有使用系統控件;其次,不能像ListView那樣使用ViewHolder結構,並且在加載大量子項時會在findViewById中花費大量時間。如果您的列表數據較少,您不妨嘗試這種方法,只不過不能使用ViewHolder結構,該結構與ListView幾乎相同。
方法4…比方法3簡單,代碼更少。同時,它保留了ListView的所有原始方法,包括notifyDataSetChanged方法。與其他方法相比,它是最完美的方法,但需要將ScrollView設置為在活動中滾動到頂部。如果妳還在猶豫,選擇這個方法,我想我以後只會用這個方法…