上一篇講了ListView的基本使用方式,是通過SimpleAdapter適配器來設置,但是這種方法無法實現在每一行添加按鈕,添加圖片,動態添加或者刪除某一行,或者使滾動條自動滑動到最底端的要求,因此需要自定義一個適配器類繼承BaseAdapter進而實現更加豐富的方法。
另外由於這已經不是我第一次做這個總結了,為了增加一點挑戰,這次就做一個 QQ好友列表的ListView
首先還是xml布局文件,在其中添加ListView控件:
主布局layout_main.xml
復制代碼
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="#00aaff"
tools:context=".MainActivity" >
<TextView
android:id="@+id/myText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="聯系人"
android:textSize="7pt"
android:layout_centerHorizontal="true"
android:textColor="#ffffff"
android:textStyle="bold" />
<ListView
android:id="@+id/qq_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/myText"/>
</RelativeLayout>
復制代碼
然後是每一行ListItem的布局,采用LinerLayout布局,一些注意的點都在裡面:
復制代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#efefef" >
<!-- LinerLayout有比較奇怪的性質:當布局中的控件可以超出布局規定的大小 ,所以這裡一行的行寬改成由內部的幾個控件
控制,而LinerLayout的layout_height改成wrap_content .. -->
<ImageButton
android:id="@+id/ct_photo"
android:layout_height="70dip"
android:layout_width="70dip"
android:layout_margin="5dip"
android:background="@drawable/contact_0"/>
<TextView
android:id="@+id/ct_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip"
android:layout_toRightOf="@id/ct_photo"
android:layout_alignTop="@id/ct_photo"
android:text="為你我受冷風吹"
android:textSize="8pt"
android:textStyle="bold"
android:maxLength="7"/>
<TextView
android:id="@+id/ct_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip"
android:layout_toRightOf="@id/ct_photo"
android:layout_alignBottom="@id/ct_photo"
android:text="為什麼受傷的總是我"
android:textColor="#888888"/>
<!-- 注意不是layout_padding -->
</RelativeLayout>
復制代碼
因為這裡使用的是自己定義的MyAdapter類,可以更靈活的實現列表的一些功能,比如和數據庫相聯系,動態更新數據、添加按鈕控件等等,在本例中模仿QQ列表為頭像設置成了ImageButton,後面的附圖中的一個Toast信息就是點擊圖像做出的相應,當然點擊一行也可以做出相應,這個後續可能會對QQ程序做一些擴展,如增加網絡模塊,聊天窗口等等。到時候再進一步討論。
下面是MyAdapter類,這個類最好和MainActivity類放在同一個包裡。
復制代碼
package com.example.android_qqlist;
import java.util.*;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.*;
public class MyAdapter extends BaseAdapter{
private Context context=null;
private int resources;
private ArrayList<HashMap<String,Object>> list=null;
private String[] from;
private int[] to;
/**
* 這裡仿照的是SimpleAdapter的形參列表
* @param context
* @param Resources
* @param list
* @param from
* @param to
*/
public MyAdapter(Context context, int resources,
ArrayList<HashMap<String, Object>> list, String[] from, int[] to) {
super();
this.context = context;
this.resources = resources;
this.list = list;
this.from = from;
this.to = to;
}
/**
* 剩下的問題就是依次實現BaseAdapter的這幾個類方法就可以了
*/
@Override
public int getCount() { //這個方法返回的是ListView的行數
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int arg0) { //這個方法沒必要使用,可以用getItemId代替
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int itemId) { //點擊某一行時會調用該方法,其形參由安卓系統提供
// TODO Auto-generated method stub
return itemId;
}
/**
* getView方法為系統在繪制每一行時調用,在此方法中要設置需要顯示的文字,圖片,
* 以及為按鈕設置監聽器。
*
* 形參意義:
* position:當前繪制的item 的位置(ID);
* convertView,系統在繪制ListView時,如果是繪制第一個Item(即第一行),convertView為null,當
* 繪制第二個及以後的Item的convertView不為空,這時可以直接利用這個convertView的getTag()方法,獲得各控件
* 的實例,並進行相應的設置,這樣可以加快繪圖速度。
*
* 為了為convertView設置附加信息Tag,這裡創建一個內部類ViewHolder,用於盛放一行中所有控件的引用,將這些引用
* 實例化後作為convertView的附加信息。
*/
class ViewHolder{
public ImageButton ctPhoto=null;
public TextView ctName=null,ctSign=null;
/*
* 從這裡可以看出,from和to數組彼此之間的元素應該一一對應,同時from和to各自元素內部的順序不同,最後ListView
* 呈現的位置也會不同!
*/
public ViewHolder(View convertView){
ctPhoto=(ImageButton)convertView.findViewById(to[0]);
/*注意View和Activity都屬於容器類,都需要設置布局文件,內部都含有子控件,且都有findViewById()
* 他們之間沒有明顯的繼承關系
*/
ctName=(TextView)convertView.findViewById(to[1]);
ctSign=(TextView)convertView.findViewById(to[2]);
}
}
class ImageListener implements OnClickListener{
private int position;
public ImageListener(int position){
this.position=position;
} //構造函數沒有返回值
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String str=list.get(position).get(from[1]).toString();
Toast.makeText(context,str+" is Clicked" , Toast.LENGTH_LONG).show();
}
}
@Override
public View getView(int position, View convertView, ViewGroup arg2) {
// TODO Auto-generated method stub
/**
* 首先判斷是不是第一次創建Item,若是,則創建convertView實例和ViewHolder對象,並通過fandViewById()方法
* 獲得每一行中所有空間的實例放在ViewHolder對象中,然後對convertView設置標簽
*/
ViewHolder viewHolder=null;
//注意convertView不是隨意創建的,需要有LayoutInflater,根據list_item布局文件創建
if(convertView==null){
LayoutInflater inflater=LayoutInflater.from(context);
convertView=inflater.inflate(resources,null); //這裡的null是一個ViewGroup形參,基本用不上
viewHolder=new ViewHolder(convertView);
convertView.setTag(viewHolder);
}
else{
viewHolder=(ViewHolder)convertView.getTag(); //通過getTag()方法獲得附加信息
}
/**
* 這裡對viewHolder中的各個控件進行相應的設置
*/
/**
* @author DragonGN
* 這裡出現了一個問題:在繪制當前行的ListItem時,只需要對當前行的控件進行設置,因此這裡不能加一個for
* 循環對每一個list中的每一個元素進行遍歷,而應該根據當前創建的ListItem行的position,然後
* 訪問數據庫list中相應位置的Map的數據,進行控件的設置!
*/
/**
* 注意這裡必須是setBackgroundDrawable() 而不是setBackground(),後者會報錯,盡管前者過期了但一樣可用
*/
viewHolder.ctPhoto.setBackgroundDrawable((Drawable)(list.get(position).get(from[0])));
//Map中要添加一個Drawable對象,這裡的from和to中的元素應該一一對應,其順序也應該對應ViewHolder構造方法中控件的調用的順序
viewHolder.ctName.setText((String)(list.get(position).get(from[1])));
viewHolder.ctSign.setText((String)(list.get(position).get(from[2])));
viewHolder.ctPhoto.setOnClickListener(new ImageListener(position));
return convertView; //把這個每一行的View對象返回
}
}
復制代碼
最後就是MainActivity類了,與因為MyAdapter的封裝方式與SimpleAdpter是一樣額,因此這裡MainActivity的操作基本不變。
復制代碼
package com.example.android_qqlist;
import java.util.*;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.ListView;
public class MainActivity extends Activity {
//每一列的列名/Map的鍵名 和其對應的View子控件的ID
String[] from={"userPhoto","userName","userSign"}; //這裡的內容對應後面HashMap中的鍵
int[] to={R.id.ct_photo,R.id.ct_name,R.id.ct_sign};
//整個ListView所顯示的全部信息和資源數組
int[] photoRes={R.drawable.contact_0,R.drawable.contact_1,R.drawable.contact_2,R.drawable.contact_3};
String[] strName={"暗夜之殇","街角的幸福","靜悄悄","憤怒的小胖"};
String[] strSign={"Where is my love...","有些事終於想開了","總有一天會尋找到自己的幸福","誰再叫我小胖我跟誰急..."};
//數據鏈表和Map容器
ArrayList<HashMap<String,Object>> list=null;
HashMap<String,Object> map=null;
ListView listView=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=(ListView)findViewById(R.id.qq_list);
list=new ArrayList<HashMap<String,Object>>();
for(int i=0; i<4; i++){
map=new HashMap<String,Object>(); //map調用put方法添加鍵值對
map.put("userPhoto",getResources().getDrawable(photoRes[i]));
map.put("userName", strName[i]);
map.put("userSign",strSign[i]);
list.add(map);
}
//創建自定義的MyAdapter對象
MyAdapter adapter=new MyAdapter(this,R.layout.list_item,list,from,to);
//調用ListView的setAdapter()方法設置適配器
listView.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
復制代碼