上一篇講了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; } }
最后附上結果圖[感覺比幾個月前初學時有點更美觀了一些]
這幾個頭像是我自己下載的圖片,其對應的資源地址在 MainActivity中用一個 photoRes數組表示的~
關於事件響應的一些問題等進一步擴展項目時再續。