Android中ListView這個組件比較常用,但對初學者來說,又比較難掌握,在此分享一下我的使用經驗。
ListView是以列表的形式展示數據,這裏面有三個要素:數據、視圖、適配器。
常用的適配器有三種:ArrayAdapter, SimpleAdapter, SimpleCursorAdapter。
其中SimpleAdapter擴展性最好,幾乎能實現所有展示需求的列表,我在實際開發中用的全是這個,這裏也只介紹這個。
假設要實現如下效果的列表視圖:
下面一步步來實現。
首先設計視圖,主要設計ListView裏面item的顯示效果,在layout中創建item.xml文件,如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="75dp" android:paddingLeft="10dp" android:paddingRight="10dp"> <ImageView android:id="@+id/img" android:layout_height="fill_parent" android:layout_width="60dp" android:layout_alignParentLeft="true" /> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:layout_width="fill_parent" android:layout_toRightOf="@id/img" android:paddingLeft="8dp"> <TextView android:id="@+id/title1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#cbcaca" android:textSize="20dp" /> <TextView android:id="@+id/title2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#cbcaca" android:textSize="14dp" /> <TextView android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#cbcaca" android:textSize="12dp" /> </LinearLayout> <CheckBox android:id="@+id/checked" android:layout_height="fill_parent" android:layout_width="wrap_content" android:layout_alignParentRight="true" android:checked="false" android:focusable="false" /> </RelativeLayout>
此xml文件定義列表中每個項目的佈局,如果想要不同的佈局,修改此文件的佈局即可。
這個文件中給每個需要在程序中動態賦值的地方都取了id,看到後面的代碼時,注意對應關係。
然後是適配器和數據,這兩個聯繫比較緊密,就放一起了。
先上代碼:
//獲取ListView對象
ListView mListView = (ListView)findViewById(R.id.listview);
//下面是數據映射關係,mFrom和mTo按順序一一對應
String[] mFrom = new String[]{"img","title1","title2","time"};
int[] mTo = new int[]{R.id.img,R.id.title1,R.id.title2,R.id.time};
//獲取數據,這裏隨便加了10條數據,實際開發中可能需要從數據庫或網絡讀取
List<Map<String,Object>> mList = new ArrayList<Map<String,Object>>();
Map<String,Object> mMap = null;
for(int i = 0;i < 10;i++){
mMap = new HashMap<String,Object>();
mMap.put("img", R.drawable.icon);
mMap.put("title1", "標題");
mMap.put("title2", "副標題");
mMap.put("time", "2011-08-15 09:00");
mList.add(mMap);
}
//創建適配器
SimpleAdapter mAdapter = new SimpleAdapter(this,mList,R.layout.item,mFrom,mTo);
mListView.setAdapter(mAdapter);
這裏要注意對應關係,Layout中的id,程序中對它的引用,Map中的數據。
到這裏已經實現了上面那張圖的效果。但是程序需要一些交互操作,比如單擊某一項,長按某一項,怎麼辦?
看下面:
添加點擊事件:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@SuppressWarnings("unchecked")
//獲取被點擊的item所對應的數據
HashMap<String,Object> map = (HashMap<String, Object>) parent.getItemAtPosition(position);
//下面是你的其他事務邏輯
}
});
這裏可以通過position獲取被點擊的item所對應的Map數據,拿到這個數據了,還怕有什麼功能實現不了嗎?
舉個最常見的例子,數據是從數據庫裏取的,每條數據有唯一的id,點了某一項之後,需要得到這個id進行數據操作,怎麼辦?很簡單,在準備數據的時候,往Map中多添加一條數據,map.put("id",id),在上面的事件處理中,可以通過(Long) map.get("id")來獲取id(此處假設id是long類型),取到id之後就可以進行數據庫操作了。放心,添加的額外數據不會影響View的顯示,因爲有對應關係在。
長按事件和這個類似,無非是註冊OnItemLongClickListener事件,在此就不列代碼了。
還有最後一個問題,界面中每個item後面顯示了一個單選框,明顯是爲批處理留的,該如何實現呢?
仔細想一下,會發現難點在於單選框狀態的記錄及獲取。下面是我的方法:
在定義SimpleAdapter對象的時候,重寫它的getView方法。如下:
mAdapter = new SimpleAdapter(this, pictureList, R.layout.picturelist, mFrom, mTo){
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
@SuppressWarnings("unchecked")
final HashMap<String,Object> map = (HashMap<String, Object>) this.getItem(position);
//獲取相應View中的Checkbox對象
CheckBox checkBox = (CheckBox)view.findViewById(R.id.checked);
checkBox.setChecked((Boolean) map.get("checked"));
//添加單擊事件,在map中記錄狀態
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
map.put("checked", ((CheckBox)view).isChecked());
}
});
return view;
}
};
在其他地方獲取狀態並處理:
//獲取列表中的項目總數
int count = pictureListView.getCount();
Map<String,Object> map = null;
boolean isChecked;
long id;
for(int i = 0;i < count;i++){
map = (Map<String, Object>) pictureListView.getItemAtPosition(i);
isChecked = (Boolean) map.get("isChecked");
if(isChecked){
id = (Long) map.get("id");
//被選中的邏輯
}
else{
id = (Long) map.get("id");
//未被選中的邏輯
}
}
//如果操作過程中對列表內容進行了添加或刪除,需要調用下面這個方法來更新視圖
mAdapter.notifyDataSetChanged();
還有一點忘了寫了,demo中的圖片是drawable裏面的圖片,如果map中只有圖片的地址,如何把它轉成drawable對象顯示出來呢?我在這裏也研究了好久,map中放入drawable對象傳過去好像沒用,不會顯示,怎麼辦?好在前面有重寫SimpleAdapter的getView方法。map中把圖片地址放進去,在getView方法裏面,把此地址轉成drawable對象,然後設置給ImageView,大功告成!代碼如下:
mAdapter = new SimpleAdapter(this, pictureList, R.layout.picturelist, mFrom, mTo){
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
@SuppressWarnings("unchecked")
final HashMap<String,Object> map = (HashMap<String, Object>) this.getItem(position);
ImageView imageView = (ImageView)view.findViewById(R.id.img);
FileInputStream fin;
try {
if(map.get("img") == null){
throw new IOException();
}
fin = getApplicationContext().openFileInput((String) map.get("img"));
imageView.setImageDrawable(Drawable.createFromStream(fin, "src"));
fin.close();
} catch (FileNotFoundException e) {
imageView.setImageResource(R.drawable.default);
} catch (IOException e) {
imageView.setImageResource(R.drawable.default);
}
return view;
}
};