写点什么

Android 进阶 (十四)Android Adapter 详解

  • 2022 年 8 月 14 日
    江苏
  • 本文字数:6014 字

    阅读完需:约 20 分钟

Android进阶(十四)Android Adapter详解

一、前言

Android 是完全遵循 MVC 模式设计的框架,ActivityControllerlayoutView。因为layout五花八门,很多数据都不能直接绑定上去,所以 Android 引入了Adapter这个机制作为复杂数据展示的转换载体,所以各种Adapter只不过是转换的方式和能力不一样而已。 


Adapter是将数据绑定到 UI 界面上的桥接类。Adapter负责创建显示每个项目的子View和提供对下层数据的访问。 


在多数情况下,你不需要创建自己的AdapterAndroid提供了一系列Adapter来将数据绑定到UI Widget上。 


因为Android负责提供数据和选择用于显示每个项目的View,所以Adapter能快速地修改要绑定的控件的外观和功能。


Adapter是用来帮助填充数据的中间桥梁,比如通过它将数据填充到ListView, GridView, Gallery


而 android 提供了以下几种:Adapter:ArrayAdapter,BaseAdapter,CursorAdapter, HeaderViewListAdapter,ListAdapter,ResourceCursorAdapter,SimpleAdapter, SimpleCursorAdapter, SpinnerAdapter, WrapperListAdapter


根据数据来源形式的不同可以选择不同的Adapter,比如数据来源于一个Arraylist 就使用 BaseAdapter,SimpleAdapter,而数据来源于通过查询数据库获得 Cursor 那就使用。

几种常用的Adapter

 


二、Adapter 体系介绍

首先来看一下Adapter的体系结构:


一个 Adapter 的对象扮演一个桥梁的角色。这个桥梁连接着一个AdapterView和它所包含的数据。Adapter提供了一个通到数据项的途径。Adapter还负责为在数据集里的每个数据生项生成一个 View。它有一个重要的方法:

public abstract View getView (int position,View convertView,ViewGroup parent)
复制代码

这个方法被setListAdapter(adapter)间接地调用。getView 方法的作用是得到一个 View,这个 view 显示数据项里指定位置的数据,你可以手动创建一个 view 或者从一个 XML layout 中 inflate。


当这个 view 被 inflated,它的父 view(如GridView,ListView等)将要使用默认的 layout 参数,除非你用inflate(int,android.view.ViewGroup,boolean)方法来指定一个根 view 并防止附着在根上。


下面分别讲一下它的几个常见的子类:

  • ListAdapter 接口继承于 Adapter。ListAdapter是一个 ListView 和 list 上的数据之间的桥梁。数据经常来自于一个 Cursor,但这不是必须的。ListView 能显示任何数据,只要它是被一个 ListAdapter 包装的。

  • BaseAdapter 抽象类:是一个实现了既能在ListView(实现了 ListAdapter 接口)和 Spinner(实现了 Spinner 接口)里用的 Adapter 类的一般基类。

  • ArrayAdapter 类一个管理这样的ListView的 ListAdapter:这个 ListView 被一个数组所支持。这个数组可装任意对象。默认状态下,这个类预期能这样:提供的资源 id 与一个单独的 TextView 相关联。如果你想用一个更复杂的 layout,就要用包含了域 id 的构造函数。


这个域 id 能够与一个在更大的 layout 资源里的 TextView 相关联。它将被在数组里的每个对象的 toString()方法所填满。你可以添加通常对象的 lists 或 arrays。重写你对象的 toString()方法来决定 list 里哪一个写有数据的 text 将被显示。


如果想用一些其它的不同于 TextView 的 view 来显示数组(比如 ImageViews),或想有一些除了 toString()返回值所填在views里的以外的数据,你就要重写getView(int,View,ViewGroup)方法来返回你想要的 View 类型。


  • SimpleCursorAdapter 类SimpleCursorAdapter 绑定 View 到 Content Provider 查询返回的游标上。指定一个 XML layout 定义,然后将数据集中的每一列的值绑定到 layout 中的一个 View 上。 

  • SimpleAdapter 类一个使静态数据和在 XML 中定义的 Views 对应起来的简单 adapter。你可以把 list 上的数据指定为一个 Map 范型的 ArrayList。ArrayList 里的每一个条目对应于 list 里的一行。Maps 包含着每一行的数据。


你先要指定一个 XML,这个 XML 定义了用于显示一行的 view。你还要指定一个对应关系,这个对应关系是从 Map 的 keys 对应到指定的 views。


绑定数据到 views 发生在两个阶段:如果一个 simpleAdapter.ViewBinder 是可用的,那么SetViewValue(android.view.View,Object,String)要被调用。如果返回 true,那么绑定发生了。如果返回 false,那么如下 views 将被按顺序地尝试:

  1. 实现了Checkable的 View(如CheckBox),预期的绑定值是 boolen。

  2. TextView,预期的绑定值是 String,并且 SetViewText 方法被调用。

  3. ImageView,预期的绑定值是一个资源的 id 或 String。并且 SetViewImage 方法被调用。


如果没有合适的绑定被发现,一个IllegalStateException被抛出。

 


不论是那种适配器模式,也不管是Listview也好还是 gridview 也好,对他们填充数据,都是分三步走。

第一:创建一个数据填充的对象,可以是 ListView, GridView, Gallery。

listView=(ListView) findViewById(R.id.listview_simple); 
复制代码

第二步:创建一个数据填充器,可以是 BaseAdapter、SimpleAdapter,也可以是与数据库相关联的CursorAdapter


例如:SimpleAdapter可以使用系统封装好的,你也可以自己去继承一个 Simpleadapter,来重写其中的方法。继承 simpleadapter的好处在于,你可以对 listitem 中每个单一的控件设置监听事件等等一系列操作。如果用的是系统封装好的就有点爱莫能助了。

直接使用系统封装的:

SimpleAdapter simpleAdapter = new SimpleAdapter(this,data, R.layout.simple_item, new String[] { "name", "info" },  new int[] { R.id.simple_name, R.id.simple_info }); 
复制代码

重写系统的simpleadpter

public class ListSimpleAdpter extends SimpleAdapter{
//要使用到的数据源 
private List<MAP><STRING,object="">> data=new ArrayList<MAP><STRING, object="">>(); 
//填充数据的资源文件 
private int resource;
private String[] from; 
private Context context;
public ListSimpleAdpter(Context context, List<MAP><STRING, object="">> data, int resource, String[] from, int[] to) { 
super(context, data, resource, from, to); 
this.context=context; 
this.data=data; 
this.resource=resource; 
this.from=from; } 
//item的总行数
@Override 
public int getCount() { 
return data==null?0:data.size();

//item对象 
@Override 
public Object getItem(int position) { 
return position;
}
//item的id
@Override 
public long getItemId(int position) { 
return position; 

//绘制每一个item 
@Override 
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder = null;
if(convertView==null) { 
convertView=LayoutInflater.from(context).inflate(resource, null); 
holder=new Holder(); 
holder.imageView=(ImageView) convertView.findViewById(R.id.listitem_pic); 
holder.title=(TextView) convertView.findViewById(R.id.listitem_title);
holder.content=(TextView) convertView.findViewById(R.id.listitem_content);
convertView.setTag(holder); 
} else { 
holder=(Holder) convertView.getTag(); 

holder.imageView.setImageResource(Integer.parseInt(data.get(position).get(from[0]).toString()));
holder.title.setText(data.get(position).get(from[1]).toString()); 
holder.content.setText(data.get(position).get(from[2]).toString()); return convertView;
 }   
class Holder{ 
ImageView imageView; 
TextView title; 
TextView content;

}
复制代码

第三步:将数据填充到对象中去

listView.setAdapter(simpleAdapter); 
复制代码

这样就完成了数据填充器的数据填充。

下面是一个SimpleAdapter的例子:

/*--------------------TestUIList.java---------------------------*/
public class TestUIList extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView list = (ListView) findViewById(R.id.MyListView);
ArrayList> lstImageItem = new ArrayList>();
String[] str1 = { "row one", "row two", "row three", "row four" };
String[] str2 = { "第一行", "第二行", "第三行", "第四行" };
for (int i = 0; i < str1.length; i++) {
HashMap map = new HashMap();
map.put("ItemImage", R.drawable.icon);
map.put("ItemTitle", str1[i]);
map.put("ItemText", str2[i]);
lstImageItem.add(map);
}
SimpleAdapter saImageItems = new SimpleAdapter(this,
lstImageItem,
R.layout.simple_list_item_2,
new String[] { "ItemImage", "ItemTitle", "ItemText" },
new int[] { R.id.ItemImage, R.id.ItemTitle, R.id.ItemText });
list.setAdapter(saImageItems);
}
}
复制代码

如果你的 ListView 的每一行想实现被点击后有响应事件。最省事的方法是继承一个 ListActivity。


ListActivity是一个这样的Activity:这个 Activity 能通过绑定到一个像 array 或 cursor 这样的数据源来显示一些 items 的 list,并且当用户选了一个 item 时,能够暴露事件句柄。


ListActivity拥有一个ListView对象。这个ListView对象能够被绑定到不同的数据源,特别是一个数组或者一个拥有查询结果的CursorListActivity有三种用法,分别是Binding,Screen LayoutRow Layout。下面仅讨论一下Screen Layout


ListActivity有一个默认的 layout。这个 layout 是由一个在屏幕中央的、单独的、全屏的 list 构成。然而,如果你想的话,你可以通过在onCreate()里调用setContentView()方法来设置你自己的 view layout 的方式制定屏幕 layout。


要这样做,你自己的 view 必须包含一个 id 为“@android:id/android:list”(或者在代码中有 list 对象)。


当你制定这个 view 是空的时,你能够包含任何类型的 view 对象来显示。这个“空 list”通知者必须有一个id“android:empty”


注意,最后一定要调用setListAdapter(adapter)方法来把通过Adapter绑定了数据的这个 List 显示出来。setListAdapter方法间接调用了Adapter的 getView 方法,其作用是返回你想要的 view 类型。


而且当点击listView里的 item 时,会根据getView重画这个ListView。想要实现事件监听,就要重写 protected void onListItemClick(ListView l, View v, int position,long id)方法。


想要把在 XML 中自定义了一行的 view 逐行显示在ListActivity中自定义的 ListView 中,并且在每行动态绑定数据的话,一般要自己写一个MyAdapter类,这个 Adapter 继承BaseAdapter并且其构造函数中至少有一个 List 参数来实现动态绑定数据。有两个重要的步骤:

  1. 重写 getView 方法,其中一重要步骤就是用 items.get(position)方法来获得被传入的数据。其中 items 是一个 List,它被赋了传入的 List 参数的值。position 是这个数据在 ListView 中的行数。Get 返回的是 E 类型.即 List 中的模板类型。

  2. 写一个内部类private class ViewHolder。这个内部类只有成员变量,它们就是你想在ListView中的一行里要显示的小 View 成分。

三、Adapter 应用实战

要想在Adapter中动态传入其它类的数据,就要在构造函数中再增加一个(或更多)List 参数。


最后给出自己写的 MyAdapter 配合 ListActivity 实现监听事件的例子:

/*--------------------MyHome.java-----------------------*/
public class MyHome extends ListActivity
{
private List items = null;
protected void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.main);
items = new ArrayList();
String[] titles = { "默认主题", "主题A", "主题B", "中秋佳节", "粉红女郎", "花样年华" };
for (int i = 0; i < titles.length; i++)
{
String title = titles[i];
items.add(title);
}
setListAdapter(new MyAdapter(this, items));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id)
{
new AlertDialog.Builder(MyHome.this).setItems(
R.array.items_my_dialog, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichcountry)
{}
}).show();
}
}
/*--------------------MyAdapter.java-----------------------*/
public class MyAdapter extends BaseAdapter
{
private LayoutInflater mInflater;
private Bitmap icom_theme;
private Bitmap icon_selected32;
private List items;
public MyAdapter(Context context, List it)
{
mInflater = LayoutInflater.from(context);
items = it;
icom_theme = BitmapFactory.decodeResource(context.getResources(),R.drawable.theme);
icon_selected32=BitmapFactory.decodeResource(context.getResources(), R.drawable.selected32);
}
public int getCount()
{
return items.size();
}
public Object getItem(int position)
{
return items.get(position);
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
convertView = mInflater.inflate(R.layout.file_row, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.mTheme = (ImageView) convertView.findViewById(R.id.theme);
holder.mSelected32 = (ImageView) convertView.findViewById(R.id.selected32);
holder.mTheme.setImageBitmap(icom_theme);
String title = items.get(position);
holder.text.setText(title);
holder.mSelected32.setImageBitmap(icon_selected32);
return convertView;
}
private class ViewHolder
{
TextView text;
ImageView mTheme;
ImageView mSelected32;
}
}
复制代码

四、Adapter 应用总结

Adapter在 Android 中占据一个重要的角色,它是数据和UI(View)之间一个重要的纽带。在常见的View(ListView,GridView)等地方都需要用到Adapter。下图直观的表达了Data、Adapter、View三者的关系。



发布于: 刚刚阅读数: 4
用户头像

No Silver Bullet 2021.07.09 加入

岂曰无衣 与子同袍

评论

发布
暂无评论
Android进阶(十四)Android Adapter详解_android_No Silver Bullet_InfoQ写作社区