Android 適配器教程(四)
之前我們學習了什麼是適配器,並且三種常用的安卓原生適配器也講完了,接下來我們就要自定義適配器了,自定義的適配器能適應更多的情況,功能更加強大,當然也需要我們更加深入的學習才能應用自如。
終於到自己寫一個適配器的時候了!
我準備了兩個例子,一個簡單一些,一個複雜一些,這次先看個簡單的:
我還是繼續在前三次的Demo項目上繼續添加例子,最後一篇的時候把源碼分享給大家~
讓我們繼續一步步寫下去。
這個例子是在ListView上麵添加按鈕,具體來說是顯示一個按鈕和一個圖片,兩行字。這個小問題涉及到的知識挺多的。也許你會想:添加按鈕首先要寫一個有按鈕的xml文件,然後用教程(三)的方法定義一個適配器,然後將數據映射到布局文件上。但是事實並非這樣,因為按鈕是無法映射的,即使你成功的用布局文件顯示出了按鈕也無法添加按鈕的響應,這時就要研究一下ListView是如何現實的了,而且必須要重寫一個類繼承BaseAdapter。
首先我們先了解一下自己寫適配器的原理:
第一步要首先重寫一個類繼承BaseAdapter
先讓我們看一下各個方法:
(1)首先是getCount()方法,這個方法要返回你要添加進ListView裏的東西的總數,
也就是要告訴ListView,我添加進列表裏的東西有多少,需要多長的列表。
這裏的mArray就是一個簡單的List<String>,
可能有人會問,為什麼不直接返回mListView的長度呢?
原因就是我們的ListView可能會添加”頭“和”尾“,來進行一些更新之類的交互,
就像微博之類的下拉刷新或者到底後加載,所以幹脆直接用我們添加的內容的長度
@Override
public int getCount() {
return mArray == null ? 0 : mArray.size();
}
(2)接下來就是getItem(int position)方法了
ListView要加載內容,要獲得內容才可以加載!
這個方法就是要讓ListView可以通過一個position來獲得我們要添加在相應位置的內容的
內容是什麼?當然是剛才mArray裏相應位置的東西啦!
@Override
public Object getItem(int position) {
return mArray.get(position);
}
(3)然後是getItemId(int position)方法,這個方法應該是為了方便ListView進行管理的,
簡單說,我們就按原來的position來讓他管理,原本是幾就是幾,省事,直接返回position!
@Override
public long getItemId(int position) {
return position;
}
最後,重頭戲!getView()方法!
這裏要實現的東西就比較多了
這個也很好理解,個人的理解就是ListView要方便的得到自己裏麵的每個View
不然人家怎麼知道你的mArray裏的數據,要怎麼填入ListView裏的每個View
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
這裏麵一般我們會怎麼做呢?
一般,我們添加到ListView裏的每個View都是xml定義好的。
一開始需要構造過來一個Context!
通過LayoutInflater.from(context).inflate(R.layout.你定義的xml,null);
獲得你要添加進去的View來賦給convertView
如果我們定義的xml裏有一個TextView
那我們就TextView tTextView = (TextView)convertView.findViewById(R.id.你的textview);
這樣就可以通過position,在mArray裏找到我們相應位置的內容,讓TextView顯示出來
當然,最後要return convertView
把這個我們包裝好的View給回ListView,讓它在列表裏顯示。
下麵是具體實現的過程:
項目開始:
也還是先在activity_main.xml裏添加一個button,一會跳轉的時候使用。
然後新建一個類MyAdapterDemo繼承自Activity作為我們第四個個例子的Activity,@Override 我們的onCreate方法。
新建一個xml文件myadapterdemo.xml作為我們的布局文件,其中也是包含一個文本域和一個ListView:
代碼如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="這是myadapter的一個例子" > </TextView> <ListView android: android:layout_width="wrap_content" android:layout_height="wrap_content" > </ListView> </LinearLayout>
然後需要定義好一個用來顯示每一個列內容的xml
Listitem2.xml 包含橫向的圖片與文字還有一個button,
Listitem2.xml:代碼如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <ImageView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px" /> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px" android:textColor="#000000" android:textSize="22px" /> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px" android:textColor="#000000" android:textSize="15px" /> <Button android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="more" /> </LinearLayout>
自定義適配器:
新建一個類MyAdapter繼承自BaseAdapter,這時候Eclipse會提示你Override默認的方法,點擊之後就會出現上麵我所說的那些方法了。
之後創建一個ViewHolder,具體原因我會在下一次教程中仔細說明。
ViewHolder:
public final class ViewHolder{ public ImageView img; public TextView text3; public TextView text4; public Button viewBtn; }把ViewHolder作為內部類放在最後等著,一會就有用了。
構造器
然後我們再研究下我們需要外部的什麼東西,也就是構造器需要被傳進什麼參數。
我們類比一下之前原生的適配器的構造方法,自然而然的想到他們都有一個Context,同時都需要傳參數!
所以我們的MyAdapter自然也不例外了~
在之前的說明中說過,一開始需要構造過來一個Context!
隻有這樣才能通過LayoutInflater.from(context).inflate(R.layout.你定義的xml,null);
獲得你要添加進去的View來賦給convertView。
所以構造器以及必要的對象就要這樣寫:
private LayoutInflater mInflater; private List<Map<String, Object>> data; // 構造器,接收數據 public MyAdapter(Context context, List<Map<String, Object>> data){ this.mInflater = LayoutInflater.from(context); this.data = data; }
注意這有個LayoutInflater我給大家解釋一下
在實際開發中LayoutInflater這個類還是非常有用的,它的作用類似於findViewById()。不同點是LayoutInflater是用來找res/layout/下的xml布局文件,並且實例化;而findViewById()是找xml布局文件下的具體widget控件(如
Button、TextView等)。
具體作用:
1、對於一個沒有被載入或者想要動態載入的界麵,都需要使用LayoutInflater.inflate()來載入;
2、對於一個已經載入的界麵,就可以使用Activiyt.findViewById()方法來獲得其中的界麵元素。
這樣我們從構造器中得到了數據data還有context(通過LayoutInflater得到布局文件)
之後我們隻要把@Override的方法填寫完整就好了,希望這時候你還沒有忘記開始時我講過的基本知識。
不過沒關係,我會一點點講清楚的:
第一個方法getCount():
是得到長度,所以外部數據有所少就要有多長,所以返回的是data==null?0:data.size();
也就是如果外麵傳過來的數據為空,那麼長度為0,不是空,長度就是數據的數量,
注意:這裏非常有必要處理一下data==null這種情況!很多代碼這裏都沒做處理,這是很不好的習慣。
所以這個方法填好了:
@Override
public int
getCount() {
return data == null? 0:data.size();
}
第二個方法getItem(int position):
之前也說過了,這個方法就是要讓ListView可以通過一個position來獲得我們要添加在相應位置的內容的
內容是什麼?當然是data裏相應位置的東西啦!
所以這個方法也填好了:
@Override
public Object getItem(int position) {
return data.get(position);
}
第三個方法getItemId(int position) :
這個方法應該是為了方便ListView進行管理的,
沒有什麼特殊需求的話,我們就按原來的position來讓他管理,position原本是幾就是幾,直接返回position
@Override
public long getItemId(int position) {
return position;
}
這樣的話就隻剩下一個大頭了:
千唿萬喚始出來的
public View getView(int position, View convertView, ViewGroup parent)
寫到這我發現還是不得不先解釋一下ViewHolder了,我先粗略的盡量讓大家理解,因為下一講的主題就是它!
咱們一點點的分析,注意看返回值的類型,是一個View,不難想象這就是返回了List裏麵一個Item的View,Android中有個叫做Recycler(反複循環器)的構件,ListView的加載原理是這樣的:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; //首先定義一個ViewHolder對象 if (convertView == null) { //當初始化的時候 convertView是空的 holder=new ViewHolder(); //創建一個ViewHolder //通過LayoutInflater用來找res/layout/下的xml布局文件,並且實例化,這樣convertView的界麵就有了。 convertView = mInflater.inflate(R.layout.listitem2, null); //使用Activiyt.findViewById()方法來獲得其中的界麵元素。 holder.img = (ImageView)convertView.findViewById(R.id.imgview2); holder.text3 = (TextView)convertView.findViewById(R.id.text3); holder.text4 = (TextView)convertView.findViewById(R.id.text4); holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn); //將holder對象作為標簽添加到View上 convertView.setTag(holder); }else { //不為空的時候直接重新使用convertView從而減少了很多不必要的View的創建 holder = (ViewHolder)convertView.getTag(); } //然後加載數據 holder.img.setBackgroundResource((Integer)data.get(position).get("imgview2")); holder.text3.setText((String)data.get(position).get("text3")); holder.text4.setText((String)data.get(position).get("text4")); //為按鈕加監聽 holder.viewBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showInfo(); } }); return convertView; }
這樣大家就比較理解了吧,至於ViewHolder和Tag,下次會好好解釋的~
package com.example.adapterdemo; import java.util.List; import java.util.Map; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; public class MyAdapter extends BaseAdapter{ private LayoutInflater mInflater; private List<Map<String, Object>> data; private Context c; // 構造器,接收數據 public MyAdapter(Context context, List<Map<String, Object>> data){ this.c = context; this.mInflater = LayoutInflater.from(context); this.data = data; } @Override public int getCount() { return data == null? 0:data.size(); } @Override public Object getItem(int position) { return data.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; //首先定義一個ViewHolder對象 if (convertView == null) { //當初始化的時候 convertView是空的 holder=new ViewHolder(); //創建一個ViewHolder //通過LayoutInflater用來找res/layout/下的xml布局文件,並且實例化,這樣convertView的界麵就有了。 convertView = mInflater.inflate(R.layout.listitem2, null); //使用Activiyt.findViewById()方法來獲得其中的界麵元素。 holder.img = (ImageView)convertView.findViewById(R.id.imgview2); holder.text3 = (TextView)convertView.findViewById(R.id.text3); holder.text4 = (TextView)convertView.findViewById(R.id.text4); holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn); //將holder對象作為標簽添加到View上 convertView.setTag(holder); }else { //不為空的時候直接重新使用convertView從而減少了很多不必要的View的創建 holder = (ViewHolder)convertView.getTag(); } //然後加載數據 holder.img.setBackgroundResource((Integer)data.get(position).get("img")); holder.text3.setText((String)data.get(position).get("text3")); holder.text4.setText((String)data.get(position).get("text4")); //為按鈕加監聽 holder.viewBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showInfo(); } }); return convertView; } public final class ViewHolder{ public ImageView img; public TextView text3; public TextView text4; public Button viewBtn; } /** * listview中點擊按鍵彈出對話框 */ public void showInfo(){ new AlertDialog.Builder(c) .setTitle("我的listview") .setMessage("介紹...") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .show(); } }
package com.example.adapterdemo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; public class MyAdapterDemo extends Activity { private ListView lv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.myadapterdemo); lv = (ListView) findViewById(R.id.myadapterlistview); MyAdapter myadapter = new MyAdapter(this,getData()); lv.setAdapter(myadapter); } private List<Map<String, Object>> getData() { List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put("text3", "Image 1"); map.put("text4", "info 1"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 2"); map.put("text4", "info 2"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 3"); map.put("text4", "info 3"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 4"); map.put("text4", "info 4"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 5"); map.put("text4", "info 5"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 6"); map.put("text4", "info 6"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 7"); map.put("text4", "info 7"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 8"); map.put("text4", "info 8"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image 9"); map.put("text4", "info 9"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image10"); map.put("text4", "info10"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image11"); map.put("text4", "info11"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image12"); map.put("text4", "info12"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image13"); map.put("text4", "info13"); map.put("img", R.drawable.ic_launcher); list.add(map); map = new HashMap<String, Object>(); map.put("text3", "Image14"); map.put("text4", "info14"); map.put("img", R.drawable.ic_launcher); list.add(map); return list; } }
讓我們再總結一下工作原理listView在開始繪製的時候,係統首先調用getCount()函數,根據他的返回值得到listView的長度,然後根據這個長度,調用getView()逐一繪製每一行。如果你的getCount()返回值是0的話,列表將不顯示同樣return1,就隻顯示一行。
係統顯示列表時,首先實例化一個適配器(這裏將實例化自定義的適配器)。當手動完成適配時,必須手動映射數據,這需要重寫getView()方法。係統在繪製列表的每一行的時候將調用此方法。getView()有三個參數,position表示將顯示的是第幾行,covertView是從布局文件中inflate來的布局。我們用LayoutInflater的方法將定義好的listitem.xml文件提取成View實例用來顯示。然後將xml文件中的各個組件實例化(簡單的findViewById()方法)。這樣便可以將數據對應到各個組件上了。但是按鈕為了響應點擊事件,需要為它添加點擊監聽器,這樣就能捕獲點擊事件。至此一個自定義的listView就完成了。
現在讓我們回過頭從新審視這個過程。係統要繪製ListView了,他首先獲得要繪製的這個列表的長度,然後開始繪製第一行,怎麼繪製呢?調用getView()函數。在這個函數裏麵首先獲得一個View(實際上是一個ViewGroup),然後再實例並設置各個組件,顯示之。好了,繪製完這一行了。那 再繪製下一行,直到繪完為止。
如果需要ListView也加入監聽,在實際的運行過程中會發現listView的每一行沒有焦點了,這是因為Button搶奪了listView的焦點,隻要布局文件中將Button設置為沒有焦點應該就OK了。

最後更新:2017-04-03 05:39:34