1.介紹:
Adapter是連接後端數據和前端顯示的適配器接口,是數據和UI(View)之間一個重要的紐帶。很多常見的View(如:ListView,Spinner,GridView等)都需要用到Adapter。如下圖直觀的表達了Data、Adapter、View三者的關係:
注意:
(1)適配器用來將不同的數據映射到View上。不同的數據對應不同的適配器,如ArrayAdapter,CursorAdapter, SimpleAdapter等, 他們能夠將數組,遊標,Map對象列表等數據映射到View上。
(2)正是由於適配器的存在,使得ListView的使用相當靈活,經過適配器的處理後,在 view看來所有的數據映射過來都是一樣的。
(3)所有的數據和資源要顯示到ListView上都須通過適配器來完成。
(4)系統已有的適配器可以將基本的數據顯示到ListView上,但是在實際開發中這些系統已實現的適配器,有時不能滿足我們的需求, 要實現複雜的ListView可以通過(1)繼承ListView並重寫相應的方法來完成, 但更一般的做法是(2)通過繼承BaseAdapter來實現。
2.比較常用的Adapter有四類:
(1)ArrayAdapter支持泛型操作,操作最爲簡單,只能展示一行字符。
(2)SimpleAdapter有很好的可擴充性,可以自定義出各種效果。
(3)SimpleCursorAdapter可以適用於簡單的純文字型ListView,它需要將Cursor的字段和UI顯示的id對應起來。可以看作SimpleAdapter和數據庫的簡單結合,能夠方便地把數據庫的內容以列表的形式展示出來。
(4)BaseAdapter是一個抽象類,繼承它需要實現較多的方法,所以也就具有較高的靈活性。是用於ListView(實現指定的ListAdapter接口)和Spinner(實現指定的SpinnerAdapter接口)的共同實現的一個公共基類適配器。
注意:ArrayAdapter,CursorAdapter, SimpleAdapter都繼承於BaseAdapter
3.使用:
ArrayAdapter的使用方法:
(1)寫代碼定義列表中要顯示的底層數據結構,如List<String>,並向底層數據結構中填充具體數據信息,例如:
//定義List變量(提供數據)--ArrayList是List的具體實現
List<String> list = new ArrayList<String>();
//添加數據內容
list.add("測試數據--1");
list.add("測試數據--2");
list.add("測試數據--3");
list.add("測試數據--4");
list.add("測試數據--5");
list.add("測試數據--6");
list.add("測試數據--7");
list.add("測試數據--8");
list.add("測試數據--9");
(2)
創建ArrayAdapter對象
參數確定ListView每行的佈局和要顯示的底層數據
//定義ArrayAdapter
//參數-----上下文環境, ListView的每一行的佈局, 底層數據
ArrayAdapter<String> adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, list);
使用自定義佈局必須給出TextView的ID—可以包含複雜的佈局,但要求佈局中必須有一個TextView用於顯示字符串
//如果要使用自定義的佈局,必須指明TextView的ID--佈局中也可以包含除TextView之外的其它控件
ArrayAdapter<String> adapter=new ArrayAdapter<String>(this, R.layout.test, R.id._id, list);
(3)調用ListView對象的setAdapter()方法將剛創建的ArrayAdapter對象與ListView對象結合起來。
//設置ListView的Adapter對象
listview.setAdapter(adapter);
(4)需要的話,再利用setOnItemClickListener()爲ListView對象添加“點擊行”的監聽器
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//parent--ListView
// view--一行
//position--適配器中的序號
// id--row id, ListView中的序號,從0開始編號
//大部分情況下(如:ArrayAdapter 和 SimpleAdapter 中,兩者是一樣的),position和id相同
//但是,有些情況(如SimpleCursorAdapter中),row id是數據庫中的_id字段的值,與position不同
String info=list.get(position) + " was clicked!"; //取出點擊的行的內容
Toast.makeText(ArrayAdapterActivity.this, info+"--"+id, Toast.LENGTH_SHORT).show();
}
});
自己寫的一個簡單模版(佈局文件太簡單不貼了):
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class ArrayAdapterActivity extends Activity {
//定義ListView對象變量---View
private ListView listview;
//存放數據的List<String>對象---Model
private List<String> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_array_adapter);
//獲取ListView對象
listview=(ListView)findViewById(R.id.simpleListViewControll);
//定義List變量(提供數據)--ArrayList是List的具體實現
list=new ArrayList<String>();
//添加數據內容
list.add("測試數據--1");
list.add("測試數據--2");
list.add("測試數據--3");
list.add("測試數據--4");
list.add("測試數據--5");
list.add("測試數據--6");
list.add("測試數據--7");
list.add("測試數據--8");
list.add("測試數據--9");
//定義ArrayAdapter,銜接ListView和List---Controller
//參數-----上下文環境, ListView的每一行的佈局, List<String>對象
//如果要使用自定義的佈局,必須指明TextView的ID--佈局中也可以包含除TextView之外的其它控件
//ArrayAdapter<String> adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
ArrayAdapter<String> adapter=new ArrayAdapter<String>(this, R.layout.test, R.id._id, list);
//設置ListView的Adapter對象
listview.setAdapter(adapter);
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//parent--ListView
// view--一行
//position--適配器中的序號
// id--row id, ListView中的序號,從0開始編號
//大部分情況下(如:ArrayAdapter 和 SimpleAdapter 中,兩者是一樣的),position和id相同
//但是,有些情況(如SimpleCursorAdapter中),row id是數據庫中的_id字段的值,與position不同
String info=list.get(position) + " was clicked!"; //取出點擊的行的內容
Toast.makeText(ArrayAdapterActivity.this, info+"--"+id, Toast.LENGTH_SHORT).show();
}
});
}
}
SimpleCursorAdapter的使用方法 (已過期):
(1)通過數據庫查詢操作得到查詢結果 --〉Cursor對象
(2)通過startManagingCursor(cursor)使得Cursor對象的生命週期能夠和Activity自動同步
(3)創建SimpleCursorAdapter對象(新版替換爲LoaderManager 和 CursorLoader)
參數指定:每行的佈局,Cursor對象,Cursor中的列名數組,每行佈局中的控件ID--每行佈局中多個控件可單獨操作
(4)調用ListView對象的setAdapter()方法將剛創建的SimpleCursorAdapter對象與ListView對象結合起來。
(5)需要的話,再利用setOnItemClickListener()爲ListView對象添加“點擊行”的監聽器。
下面的代碼我解釋一下,我這查的是手機中的聯繫人,這種高危操作肯定要申請權限了,所以我在配置文件中寫了如下代碼:
<uses-permission android:name="android.permission.READ_CONTACTS" />
這還不能獲得權限(想想APP點擊一個按鈕後可以獲取你這臺手機的全部聯繫人,好可怕啊),還要在經過下面代碼前面一部分的權限申請判斷過程(不但如此,運行的時候你點擊按鈕後它還彈出危險提示框,問你是否允許給它這個權限,你點擊allow就是允許,這樣才能顯示)。
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
public class SimpleCursorAdapterActivity extends AppCompatActivity {
//定義ListView對象
private ListView listview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在當前Activity中動態創建ListView對象
listview=new ListView(this);
//設置Activity的顯示界面爲ListView--控件ListView充滿整個Activity
setContentView(listview);
//判斷是否已經取得讀聯繫人的權限
//ContextCompat.checkSelfPermission()檢測權限的返回值是PERMISSION_GRANTED和PERMISSION_DENIED
if(ContextCompat.checkSelfPermission(SimpleCursorAdapterActivity.this,
Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(SimpleCursorAdapterActivity.this, //如果沒有權限,則動態申請權限
new String[]{Manifest.permission.READ_CONTACTS},1);
}
//獲取讀"聯繫人"數據庫的cursor
Cursor cursor=getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
//設置Cursor對象的生命週期和Activity自動同步
startManagingCursor(cursor);
//創建Adapter對象--必須指出佈局及佈局中的TextView的ID與Cursor中的列名的對應關係
//SimpleCursorAdapter已經過時,建議使用 LoaderManager 和 CursorLoader
ListAdapter adapter=new SimpleCursorAdapter(this, //上下文環境
// android.R.layout.simple_expandable_list_item_1, //單行的佈局--使用系統定義的佈局
R.layout.test, //單行的佈局--使用自定義的佈局
cursor, //遊標對象
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
// new int[]{android.R.id.text1}); //顯示用的TextView的ID--系統佈局中定義
new int[]{R.id._id}); //顯示用的TextView的ID--自定義佈局中定義
//設置ListView對象的Adapter
listview.setAdapter(adapter);
//設置點擊行的監聽器
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String info;
// info=((TextView)view).getText().toString();//取出當前行的內容顯示, 若每行不是一個簡單的TextView,則不能如此操作
info=((TextView)((LinearLayout)view).findViewById(R.id._id)).getText().toString(); //自定義View區內容
Toast.makeText(SimpleCursorAdapterActivity.this,
info,
Toast.LENGTH_SHORT).show();
}
});
}
//權限申請結果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//ToDo:已授權
} else {
//ToDo:用戶拒絕
Toast.makeText(SimpleCursorAdapterActivity.this,"無權讀取聯繫人信息",Toast.LENGTH_SHORT).show();
}
}
}
看一下我的截圖:
我隨便加了兩個聯繫人,點擊第二個按鈕(第一次點擊會有彈出權限申請框,之後就沒了)後:
SimpleAdapter的使用方法(適合每行佈局複雜的列表):
(1)定義並填充存儲底層數據的數據結構,如:List<Map<String,Object>>, Map集合類用於存儲元素對(稱作“鍵”和“值”),其中每個鍵映射到一個值—適合於用來存儲複雜數據。
(2)創建SimpleAdapter對象
參數:每行的佈局,List對象,Map對象中的的鍵名數組,每行佈局中的控件ID
(3)設置Adapter
(4)需要的話,可以添加點擊行監聽器
廢話不多直接上代碼:
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//使用ListActivity--內部僅含有一個ListView
public class SimpleAdapterActivity extends ListActivity {
//數據線性表--List實際上是一個線性表的接口
List<Map<String,Object>> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//創建Adapter對象
SimpleAdapter adapter = new SimpleAdapter(this, //上下文對象
getData(), //存放數據的List對象
R.layout.item, //每行的佈局
new String[]{"title","info","img"}, //數據對象Map中的列名--鍵
new int[]{R.id.title,R.id.info,R.id.img}); //列內容--Listview中的控件ID,對應控件用於顯示Map對象中的值
//爲ListActivity中的ListView設置Adapter
setListAdapter(adapter);
}
//獲取List數據對象
private List<Map<String,Object>> getData(){
//建立List對象--具體的ArrayList對象
list=new ArrayList<Map<String,Object>>();
//List中存放的Map對象,由多個<鍵,值>對構成--一個Map對象對應ListView中的一行
Map<String, Object> map;
map=new HashMap<String,Object>();
map.put("title", "牛");
map.put("info", "食草動物,家畜");
map.put("img", R.drawable.cow);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "孔雀");
map.put("info", "鳥類,開屏很好看");
map.put("img", R.drawable.peacock);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "熊貓");
map.put("info", "珍稀,國寶");
map.put("img", R.drawable.panda);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "恐龍");
map.put("info", "爬行類,已滅絕");
map.put("img", R.drawable.dinosaur);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "神龍");
map.put("info", "神話中的動物");
map.put("img", R.drawable.dragon);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "北極熊");
map.put("info", "生活在極寒之地");
map.put("img", R.drawable.bear);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "牛-2");
map.put("info", "食草動物,家畜");
map.put("img", R.drawable.cow);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "孔雀-2");
map.put("info", "鳥類,開屏很好看");
map.put("img", R.drawable.peacock);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "熊貓-2");
map.put("info", "珍稀,國寶");
map.put("img", R.drawable.panda);
list.add(map);
return list;
}
//重寫此方法---點擊一行時的回調函數--參數含義同前
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
String s=list.get(position).get("title").toString(); //獲取該行的Map對象的指定屬性的數據內容
Toast.makeText(SimpleAdapterActivity.this, s, Toast.LENGTH_SHORT).show();
}
}
自定義Adapter:(適合於每行佈局複雜的列表,且可以操作該行中的控件)
(1)通過繼承BaseAdapter可以定義自己的Adapter,可以將任何複雜組合的數據和資源,以任何想要的顯示效果展示出來;
(2)BaseAdapter的使用方法與前述Adapter不一樣,前述Adapter可以直接在其構造方法中進行數據的設置,但是BaseAdapter需要實現一個繼承自BaseAdapter的子類,繼承BaseAdapter之後,一般需要重寫以下四個方法:getCount() ,getView() ,getItem(),getItemId()。
(3)getCount()和getView()這兩個方法是自定義Adapter中最爲重要的方法,只要同時重寫這兩個方法,ListView就能完全按要求顯示。而 getItem()和getItemId()方法將會在調用ListView的響應方法的時候被調用到, 如果不調用其它響應方法,也可以不重寫這2個方法;
(4)ListView繪製過程:系統在繪製ListView之前,(1)先調用getCount()方法來獲取Item的個數;(2)之後每繪製一個Item就會調用一次getView()方法,在此方法內就可以將事先定義好的xml佈局實例化,並返回一個View對象作爲一個Item顯示出來。
自定義Adapter的使用步驟:
定義並填充存儲底層數據的數據結構;
自定義一個繼承自BaseAdapter的子類MyAdapter,並實現它的2個重要方法getCount()和getView():
getCount()返回底層數據結構的大小(item個數);
getView()中動態創建ListView中的一行,按需設置該行佈局中的子控件的屬性值和監聽器。
創建MyAdapter對象;
設置Adapter;
需要的話,用setOnItemClick()方法添加點擊行監聽器。
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
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.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyAdapterActivity extends ListActivity {
//ListView的底層數據對象變量
private List<Map<String, Object>> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//獲取數據
list=getData();
//創建自定義的Adapter對象--內部數據與ListView各項的對應關係在自定義的Adapter中實現
MyAdapter adapter = new MyAdapter(this);
//爲ListActivity中的ListView設置Adapter
setListAdapter(adapter);
}
//獲取List數據對象
private List<Map<String,Object>> getData(){
//List對象
List<Map<String,Object>> list=new ArrayList<Map<String,Object>>();
//List中存放的Map對象,由多個<鍵,值>對構成--一個Map對象對應ListView中的一行
Map map;
map=new HashMap<String,Object>();
map.put("title", "牛");
map.put("info", "食草動物,家畜");
map.put("img", R.drawable.cow);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "孔雀");
map.put("info", "鳥類,開屏很好看");
map.put("img", R.drawable.peacock);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "熊貓");
map.put("info", "珍稀,國寶");
map.put("img", R.drawable.panda);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "恐龍");
map.put("info", "爬行類,已滅絕");
map.put("img", R.drawable.dinosaur);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "神龍");
map.put("info", "神話中的動物");
map.put("img", R.drawable.dragon);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "北極熊");
map.put("info", "生活在極寒之地");
map.put("img", R.drawable.bear);
list.add(map);
//只要漏出一丁點,就要調用getView(...)顯示該行
map=new HashMap<String,Object>();
map.put("title", "牛-2");
map.put("info", "食草動物,家畜-2");
map.put("img", R.drawable.cow);
list.add(map);
map=new HashMap<String,Object>();
map.put("title", "孔雀-2");
map.put("info", "鳥類,開屏很好看-2");
map.put("img", R.drawable.peacock);
list.add(map);
return list;
}
//自定義的Adapter類
/**Android系統更新ListView時需要調用相關的Adapter的方法:
* 1)更新前首先調用getCount()獲取需要更新的行數,然後更新過程逐行進行
* 2)更新每行時,需要調用getView()獲取當前行對應的View對象,
* Adapter需要在getView()方法中適時創建View對象,並對View對象填充需要顯示的內容
* */
public final class MyAdapter extends BaseAdapter {
//實例化佈局對象---用於實例化每行的佈局->View對象
private LayoutInflater mInflater;
public MyAdapter(Context context){
this.mInflater = LayoutInflater.from(context);
}
//獲取ListView的總行數
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int arg0) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
//ListView中一行對應的對象組合--容器類
//使用ViewHolder可以減少findViewById()的使用頻率,方便數據訪問
public final class ViewHolder{
public ImageView img;
public TextView title;
public TextView info;
public Button viewBtn;
}
//獲取指定的一行所對應的View對象--不存在的話則創建之
// position--當前要顯示的數據的位置(行號)
// convertView--可利用的以前的View對象(上下滾動時,利用舊View對象顯示新內容),
// 如果此項爲空,則需要動態創建新的View對象
// parent--父控件(上層的ListView)
@Override
public View getView(int position, View convertView, ViewGroup parent){
//實例化一行的佈局
/*View rowview=mInflater.inflate(R.layout.myitem, null);
//找到行內的子控件
ImageView img=(ImageView)rowview.findViewById(R.id.img);
TextView title=(TextView)rowview.findViewById(R.id.title);
TextView info=(TextView)rowview.findViewById(R.id.info);
Button btn=(Button)rowview.findViewById(R.id.view_btn);
//子控件賦值
img.setBackgroundResource((Integer)list.get(position).get("img"));
title.setText((String)list.get(position).get("title"));
info.setText((String)list.get(position).get("info"));
//綁定該行中的Button對象的監聽器
//創建監聽器對象時, 用參數傳遞當前的行號
//每行中的Button建一個監聽器對象,不同對象的position值不同
btn.setOnClickListener(new viewButtonClickListener(position)) ;
return rowview;*/
//本行對應的容器對象
ViewHolder holder = null;
//如果該行的View爲空, 則動態創建新的View
//利用已有的View顯示新數據,可以減少內存佔用,優化響應速度
if (convertView == null) {
//先創建容器對象,以便後來向其中填充當前行中的控件對象
holder=new ViewHolder();
//實例化ListView的一行, root參數爲空說明此View的父控件默認爲爲上層的ListView
convertView = mInflater.inflate(R.layout.myitem, null);
//獲取內部的各個控件對象, 保存到容器對象中, 以後直接取來用即可--每個子控件對象只用一次findViewById()
holder.img = (ImageView)convertView.findViewById(R.id.img);
holder.title = (TextView)convertView.findViewById(R.id.title);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn);
//設置容器對象爲ListView當前行的Tag--建立容器類對象與ListView當前行的聯繫
convertView.setTag(holder);
}
else { //如果該行的View已經存在,則通過標記獲取該行對應的對象
holder = (ViewHolder)convertView.getTag();
}
//設置該行內的控件對象對應的屬性,Adapter的作用(List<--->ListView)--- 如果不用ViewHolder則需要頻繁使用findViewByID
holder.img.setBackgroundResource((Integer)list.get(position).get("img"));
holder.title.setText((String)list.get(position).get("title"));
holder.info.setText((String)list.get(position).get("info"));
//綁定該行中的Button對象的監聽器
//創建監聽器對象時, 用參數傳遞當前的行號
//每行中的Button建一個監聽器對象,不同對象的position值不同
holder.viewBtn.setOnClickListener(new viewButtonClickListener(position)) ;
return convertView;//返回當前行對應的View對象
}
}
//重寫此方法---點擊ListView一行時的回調函數--參數含義同前
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
String s=list.get(position).get("title").toString(); //獲取該行的Map對象的指定屬性的數據內容
Toast.makeText(MyAdapterActivity.this, s, Toast.LENGTH_SHORT).show();
}
//使用內部類實現ListView的每行中按鈕的監聽函數
//該監聽器類會爲ListView的每一行提供一個監聽器對象,用來監聽該行中按鈕的點擊事件
class viewButtonClickListener implements View.OnClickListener {
//記錄按鈕所在的行號
int position;
//必須使用自定義的構造函數---因爲需要在此通過參數記錄該監聽器對象監聽的行號
public viewButtonClickListener(int pos) {
position=pos;
}
@Override
public void onClick(View v) {
//獲取該行的具體列的內容,並顯示之
String info=list.get(position).get("info").toString();
Toast.makeText(MyAdapterActivity.this,info, Toast.LENGTH_SHORT).show();
}
}
}