在學習筆記(十七)中,我們對ListView做了進一步的探討,然而給出的例子list中的元素可以有多個widget,並可靈活設定他們的值,但是這些widget之間缺乏互動,而且getView()的調用,需要重刷給list的entry,我們希望能夠在entry中觸發變化。
本次,我們繼續根據《Beginging Android 2》的學習,結合RatingBar,將程式稍微複雜一點。RatingBar看用於媒體庫的平級,我們用RatingBar取代了之前例子的表徵圖,當RatingBar設定為三星時,該entry後面的文本改為大寫,如果低於三星將恢複原來的小寫顯示。
例子:自訂資料結構和內部widget的觸發處理
1)Android XML檔案:用RatingBar替代之前例子的ImageView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ……>
<RatingBar android:id="@+id/c85_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars = "3" <!-- 設定三星平級方式-->
android:stepSize = "0.5"
<!--step為0.5,也就是允許2.5的星級評比 -->
android:rating = "2"/> <!-- 預設為2星-->
<TextView android:id="@+id/c85_label"
android:paddingLeft="2px"
android:paddingRight="2px"
android:paddingTop="10px"
android:textSize="24sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
2)設定自定製資料結構來儲存資訊,並提供查詢資訊的方法
在之前的例子中,我們使用了ArrayList<String>來存放每個單元的資料資訊,在這個例子中,作為更通用的方式,每個單元資訊為我們自定的類RowModel。
class RowModel{
String label; //儲存entry的當前文本顯示內容,通過調用toString()給出,如果三星將提供大寫顯示。
float rating = 2.0f; //儲存entry的星級資料,對應RatingBar的星級顯示
RowModel(String label){
this.label = label;
}
public String toString(){
if(rating >= 3.0){
return label.toUpperCase();
}
return label;
}
}
在我們的主類中,根據自訂的資料結構設定我們的資料資訊list,並匯入list adapter中,同時我們增加一個方法,根據position(index)來從資料資訊中擷取該單元的資料。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<RowModel> list = new ArrayList<RowModel>();
//步驟1:list作為資料的儲存
for(String s: items){ //步驟2:將String[] items的資訊匯入list中,這種寫法比較特別,我一般會老老實實for(int i =0; i <items.length; i++)的方式來寫。
list.add(new RowModel(s));
}
setListAdapter(new RatingAdapter(list)); //步驟3:設定自定製的listadapter(具體在後面處理),並將資訊資料list匯入其中
}
//根據List的位置,獲得具體的list元素,一般add,del,find的處理中,相當於find
private RowModel getModel(int position){
return ((RatingAdapter)getListAdapter()).getItem(position);
}
3)List單元的View和widget資訊捆綁,實現快速定位widget
根據之前的學習,為了使程式運行得更有效率,我們會使用setTag的方式,將list單元的UI的View和儲存單元UI中widget資訊的類捆綁,以便可以快速定位widget。
步驟1:設定儲存List單元View中widget的相關類。
其實,我們可以將這些widget資訊和2)中的資料資訊放在一起,在這個例子中程式會更借鑒,但是這樣的處理很不好,我們儘可能把要將UI相關的資訊和資料資訊放在一起,否則UI修改或者進行尺寸適配時出現麻煩。
private class ViewWrapper{
View base;
RatingBar rate = null;
TextView label = null;
ViewWrapper(View base){
this.base = base;
}
RatingBar getRatingBar(){
if(rate == null)
rate =(RatingBar) base.findViewById(R.id.c85_rating);
return rate;
}
TextView getLabel(){
if(label == null)
label = (TextView)base.findViewById(R.id.c85_label);
return label;
}
}
步驟2:List單元View的呈現(getView),並且提供其中widget觸發的處理
一個List單元的View對應兩個內容,一個是儲存的資料,可以通過getModel來獲得,另一個是對應的單元UI的widget隊形的儲存,通過getTag()和setTag(),這個在上一次學習中已經學習了,我們還需要增加View中widget的觸發,在這個例子中,當RatingBar的星級出現變化是,可能需要重寫重新整理後面文章的顯示。我們具體看代碼:
private class RatingAdapter extends ArrayAdapter<RowModel>{
//步驟2.1:設定建構函式,將資料資訊放入ArrayAdapter中,這樣可以通過getItem() 擷取資料資訊,同時也設定layout格式
RatingAdapter(ArrayList<RowModel> list){
super(Chapter8Test5.this,R.layout.entry,list);
}
//步驟2.2: 編寫ListView中每個單元的呈現
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewWrapper wrapper;
RatingBar ratebar = null;
//步驟2.3:如果沒有建立View,根據layout建立之,並將widget的儲存類的對象與之捆綁為tag
if(row == null){
LayoutInflater inflater=getLayoutInflater();
row = inflater.inflate(R.layout.entry, parent,false);
wrapper = new ViewWrapper(row);
row.setTag(wrapper);
//步驟2.4:在產生View的時候,添加將widget的觸發處理
ratebar = wrapper.getRatingBar();
ratebar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
//步驟2.4.1:儲存變化的資料
Integer index = (Integer)ratingBar.getTag();
RowModel model = getModel(index);
model.rating = rating;
//步驟2.4.2:設定變化
LinearLayout parent = (LinearLayout)ratingBar.getParent();
TextView label = (TextView)parent.findViewById(R.id.c85_label);
label.setText(model.toString());
}
});
}else{ //步驟2.4:利用已有的View,獲得相應的widget
wrapper = (ViewWrapper) row.getTag();
ratebar = wrapper.getRatingBar();
}
//步驟2.5:設定顯示的內容,同時設定ratingbar捆綁tag為list的位置,因為setTag()是View的方法,因此我們不能降至加在ViewWrapper,所以需要載入ViewWrapper中的widget中,這裡選擇了ratebar進行捆綁。
RowModel model= getModel(position);
wrapper.getLabel().setText(model.toString());
ratebar.setTag(new Integer(position));
ratebar.setRating(model.rating);
return row;
}
}
我們在這裡例子中進行了一個實驗,考察什麼時候convertView可以為null,一屏可以顯示0-8個row,這些list的元素都是null,需要通過程式來建立,然而當我混動螢幕的時候,我想象中,後面的元素第一次也應該為0,但是出乎我的意外,只有position=14的出現row=null。對於通過scroll螢幕的情況,下一屏Android可能根據第一屏對UI的處理情況進行了處理。因此Android對UI的智能處理情況我們不太能把握,因此任何與資料有關,不是純粹的UI問題的初始賦值的問題,不要只放置在if(row==null)中進行初始處理,否則會引起不可預測的意外。例如我們將步驟2.5中的ratebar.setTag(new
Integer(position))此句放在if(row==null)會得到不正常的結果,因為不是所有的list元素中的該widget都在初始的情況下成功進行了捆綁,所以我們將它放置在外面或者通知方式在if和else的判斷中,保證所有情況都覆蓋。
ListAdapter:CursorAdapter
一般來講,我們可以使用ArrayAdapter來適用很多情況,還有其他的Adapter,使用方式類似,但是CursorAdapter有些不一樣,通過newView()和bindView(),如果沒有建立,使用newView(),然後調用bindView(),如果已經建立,使用bindView()。
相關連結:我的Andriod開發相關文章