网站首页  词典首页

请输入您要查询的论文:

 

标题 安卓平台下通用适配器类设计与实现
范文 张延年++米洪
摘 要:介绍了安卓平台下Adapter(适配器)组件的机制和一般使用方法,详述了如何通过面向对象的编程方法实现一种可复用的、通用的Adapter类。该Adapter类的实现过程主要运用了泛型、抽象类、公用代码的抽取和封装等多种技术,使用SparseArray类实现了列表视图组件的存储,重构了一个通用的可扩展的ViewHolder类,在Adapter类的getView方法中提供了一个用于数据填充的可配置convert的抽象方法。使用Adapter类明显提高了程序开发效率,增强了系统的可扩展性和可维护性。
关键词:安卓;适配器;SparseArray类;ViewHolder类
DOIDOI:10.11907/rjdk.1511223
中图分类号:TP319
文献标识码:A 文章编号文章编号:1672-7800(2015)012-0109-05
0 引言
Android系统是完全遵循MVC模式设计的框架,其中Activity是Controller,Layout是View,因为Layout五花八门,很多数据不能直接绑定上去,所以Android引入了Adapter(适配器)机制作为复杂数据展示的转换载体。
Adapter可以认为是View与Data之间的桥梁,是连接后端数据和前端显示的适配器接口,用来处理数据并将数据绑定到View上,在使用常见的View(如ListView、GridView)编程时都需要用到Adapter。图1较为直观地表达了Data、Adapter、View三者的关系:数据源首先封装到Adapter中,再由Adapter将数据映射到ListView上显示,当用户对ListView组件操作时会通过Adapter更新数据并返回显示结果。
Android提供多种适配器类,比如BaseAdapter、ArrayAdapter、SimpleAdapter、CursorAdapter等,开发时可以根据不同数据源选用适配器类,也可以通过继承的方式自定义适配器类。其中,BaseAdapter是一个抽象类,具有较高的灵活性,一般在实际项目开发中用得比较多,因此,本文围绕BaseAdapter进行阐述。
BaseAdapter使用步骤:①在需要显示View的Activity中自定义一个内部类MyAdapter,继承于BaseAdapter,实现其中的抽象方法,一般还需要重写构造方法以方便传递数据;②在Activity中实例化MyAdapter对象并传递数据,数据可以是List集合或数组;③使用View的
图1 适配器接口(Adapter)工作机制
setAdatper(adapter)方法关联MyAdapter对象,绑定数据;④在用户添加或修改数据时使用notifydatasetchanged方法更新数据。
以上使用BaseAdapter的编程方式存在一定弊端,即界面每次显示一个不同的View都要定义一个新的MyAdapter类,代码无法复用。这种编程方式在开发简单应用时问题不大,但在开发较复杂的应用时会造成大量重复性代码,使系统的可扩展性和可维护性大大降低,严重影响开发效率。本文通过一个典型案例,介绍如何设计并实现一种通用的Adapter类,以解决上述问题。
1 Adapter一般使用方法
下面是一个手机通讯录案例,相对于实际项目在功能上作了很大程度的简化,仅由一个界面构成,界面上只显示联系人的姓名文本和头像图片,通讯录的内容为100条简单数据,头像图片都是同一张本地图片,关键代码如下:
(1)创建布局文件。通讯录布局文件:activity_main.xml,包含一个ListView组件,id属性为listview。
通讯录列表子项布局文件:listview_item.xml,包含一个ImageView组件,id属性为face,一个TextView组件,id属性为name。
(2)创建联系人实体类。AddressBook.class
public class AddressBook {
private UUID id;// 联系人编号
private String name;// 联系人姓名
private String phoneNumber;// 电话号码
private String faceName;// 头像名字

}
(3)创建数据提供者类。DataProvider.class
public class DataProvider {
public static final int count=100;//联系人数目
/* 获取通讯录中的所有联系人信息 */
public static List getAllRecord() {

}
}
(4)创建界面显示类。MainActivity.class
public class MainActivity extends Activity {

/* 创建自定义适配器内部类 */
private class MyAdapter extends BaseAdapter {

@Override
public View getView(int position,View convertView,ViewGroup parentView) {


View view;
ViewHolder holder;
if (convertView == null) { // 如果convertView为空,创建新的view
view = inflater.inflate(resource,null);
holder = new ViewHolder(); //新建一个viewholder对象
holder.name = (TextView) view.findViewById(R.id.name); //将findviewbyID的结果赋值给holder对应的成员变量
holder.face = (ImageView) view.findViewById(R.id.face);
view.setTag(holder); // 将holder与view进行绑定
} else {
view = convertView; // 否则表示可以复用convertView
holder = (ViewHolder) view.getTag();//将convertView绑定的ViewHolder取出
}
AddressBook item = list.get(position); //取得列表项的数据
holder.name.setText(item.getName()); // 直接操作holder中的成员变量即可对布局视图组件进行赋值
int imageId = getResources().getIdentifier(item.getFaceName(),
"drawable","com.demo.v20150920"); //得到图片资源的id
holder.face.setImageResource(imageId);
return view;
}
}
/* 定义静态内部类ViewHolder,用以保存列表项视图组件 */
static class ViewHolder {
TextView name;// 联系人名字
ImageView face;// 联系人头像
}
}
程序运行结果见图2。
图2 手机通讯录案例运行结果
2 通用ViewHolder实现
首先分析一下以上程序中ViewHolder对象holder,其作用是持有列表项布局中的控件,通过setTag(holder)与convertView绑定,当convertView复用时,直接通过getTag从与之对应的holder中取得convertView布局中的控件,省去了findViewById的时间。也就是说,实际上每个convertView会绑定一个holder对象,这个viewHolder主要用于帮助convertView存储布局中的控件。
只要写出一个通用的ViewHolder,然后对于任意的convertView,提供一个对象让其setTag即可。既然是通用的,这时的ViewHolder就不可能含有各种控件的成员变量,因为每个Item的布局是不同的。
解决这个问题方法:对ViewHolder类进行改进,增加一个属性来专门存储每个Item布局中的所有控件,并且在构造方法中通过参数传递的方式给其赋值。这个属性必须是一个可动态变化的集合对象,并且可以很方便地通过控件的ID获取控件。这里使用Android提供的SparseArray这个类,和Map类似。以下代码是改进后较为通用的ViewHolder类。
public class ViewHolder
{
private final SparseArray mViews;//存储列表项布局中的视图组件
private int mPosition; //列表项的位置
private View mConvertView; //可复用的列表项视图
/* 定义私有构造函数 */
private ViewHolder(Context context,ViewGroup parent,int layoutId,
int position)
{
this.mPosition = position;
this.mViews = new SparseArray();
mConvertView = LayoutInflater.from(context).inflate(layoutId,parent, false);
mConvertView.setTag(this);
}
/* 获取ViewHolder对象,如果convertView参数有值则复用 */
public static ViewHolder get(Context context,View convertView,
ViewGroup parent,int layoutId,int position)
{
if (convertView == null)
{
return new ViewHolder(context,parent,layoutId,position);


}
return (ViewHolder) convertView.getTag();
}
public View getConvertView()
{
return mConvertView;
}
/* 通过控件的Id获取对应的的控件,如果没有则加入views */
public T getView(int viewId)
{
View view = mViews.get(viewId);
if (view == null)
{
view = mConvertView.findViewById(viewId);
mViews.put(viewId,view);
}
return (T) view;
}
}
改进后的ViewHolder类优点:①不需要每次写一个需要显示列表的Acrivity;②通过SparseArray对象存储列表项视图组件,同时通过定义一个泛型视图返回getView(id)方法,抽取所有组件的findViewById操作,减少了重复代码的编写。
使用以上ViewHolder类重写案例中内部类MyAdapter的getView方法如下:
//实例化一个viewHolder
ViewHolder holder = ViewHolder.get(context,convertView,
parentView,R.layout.listview_item,position);
//通过getView获取组件
TextView name = holder.getView(R.id.name);
ImageView face = holder.getView(R.id.face);
//向组件填充数据
AddressBook item = list.get(position);
name.setText(item.getName());
int imageId = getResources().getIdentifier(item.getFaceName(),
"drawable","com.demo.v20150920");// 得到图片资源的id
face.setImageResource(imageId);
return holder.getConvertView();
}
通过分析发现,改进前的代码是getView函数+ViewHolder类,共21行代码,改进后只需要写getView函数,共10行代码,代码量减少了一半多,编程效率得到了较大提升。
3 通用Adapter实现
有了以上通用的ViewHolder,可以说比最初的案例代码有了很大的进步,但是在实现较为复杂列表界面时,还是必须每次都定义一个继承BaseAdapter的子类,而且一般这个子类作为Activity的内部类只使用一次,代码不能复用。而且越是复杂的系统这种重复性的代码就越多,造成很高的冗余度,对后期的维护和扩展造成很大困难。解决这一问题的关键是使用面向对象的编程思想,通过共性部分抽取和非共性部分参数传递的方式,实现一个可复用的通用性Adapter类,可以命名为CommonAdapter,具体实现步骤如下:
(1)Adapter一般需要保持一个List对象,存储一个Bean集合,不同的ListView,Bean肯定是不同的,这个CommonAdapter肯定需要支持泛型,内部维持一个List,这样就可以使问题得到初步解决。关键代码如下:
public abstract class CommonAdapter extends BaseAdapter
{
protected LayoutInflater mInflater;//布局填充对象
protected Context mContext; //上下文对象
protected List mDatas; //数据提供者
protected final int mItemLayoutId; //列表项视图组件资源id
public CommonAdapter(Context context,List mDatas,int itemLayoutId)
{
…//省略初始化化属性代码
}
}
(2)此时的CommonAdapter依然是一个抽象类,除了getView方法以外我们把其它的代码都实现了。因此,在这种情况下仍然需要定义一个子类并实现getView方法,并在getView里使用前面改进过的ViewHolder类。要进一步对getView方法进行封装和抽取,关键代码如下:
public abstract class CommonAdapter extends BaseAdapter
{

public View getView(int position,View convertView,ViewGroup parent)







{
final ViewHolder viewHolder = getViewHolder(position,convertView,
parent);
convert(viewHolder,getItem(position));
return viewHolder.getConvertView();
}
/* 数据填充接口 */
public abstract void convert(ViewHolder helper,T item);
/* 获取ViewHolder对象 */
private ViewHolder getViewHolder(int position,View convertView,
ViewGroup parent)
{
return ViewHolder.get(mContext,convertView,parent,mItemLayoutId, position);
}
在上面的getView中只有3句代码,第1句是获取ViewHolder对象,第3句是通过ViewHolder.getConvertView()方法返回要显示的视图,这两句代码由于是固定不变的,所以写成了硬编码。第2句convert()方法的作用是进行数据填充。由于列表项视图是不确定的,所以这部分内容通过一个抽象方法对外公布。这时,在Activity里就可以用匿名内部类来创建Adapter对象,同时在创建的时候实现convert方法,关键代码如下:
/* 设置适配器 */
listView.setAdapter(mAdapter = new CommonAdapter
getApplicationContext(),mDatas,R.layout.listview_item)
{
public void convert(ViewHolder viewHolder,AddressBook item)
{
TextView tv = viewHolder.getView(R.id.name);
tv.setText(item.getName()); //设置联系人名字
ImageView iv = viewHolder.getView(R.id.face);
int imageId = getResources().getIdentifier(item.getFaceName(),
"drawable","com.demo.v20150920");// 得到图片资源的id
iv.setImageResource(imageId);//设置图片内容
}
});
(3)通过分析,发现在列表项布局里的View常用的有以下几类组件: TextView、 ImageView,Button、CheckBox等,可以在ViewHolder类里面封装一些常用方法,比如setText(id,String)、setImageResource(viewId,resId)、setImageBitmap(viewId,bitmap)等,这样可以进一步简化以上convert方法中的代码,使得CommonAdapter具有更强的通用性,关键代码如下:
public class ViewHolder
{

/* 通过控件的Id获取对于的控件,如果没有则加入views */
public T getView(int viewId)
{
View view = mViews.get(viewId);
if (view == null)
{
view = mConvertView.findViewById(viewId);
mViews.put(viewId,view);
}
return (T) view;
}
/* 为TextView设置字符串*/
public ViewHolder setText(int viewId,String text)
{
TextView view = getView(viewId);
view.setText(text);
return this;
}
/* 为ImageView设置图片 */
public ViewHolder setImageResource(int viewId,int drawableId)
{
ImageView view = getView(viewId);
view.setImageResource(drawableId);
return this;
}
以上ViewHolder中类似setText、setImageResource等方法,可以在实际项目开发过程中不断增加和完善,这样做既可以加强CommonAdapter的通用性,又不会影响已存在代码的正常运行。
使用以上ViewHolder类应用到Activity创建Adapter对象中,会发现代码进一步减少,关键代码如下:


/* 设置适配器 */
listView.setAdapter(mAdapter = new CommonAdapter
getApplicationContext(),mDatas,R.layout.listview_item)
{
@Override
public void convert(ViewHolder viewHolder,AddressBook item)
{
viewHolder.setText(R.id.name,item.getName()); //设置联系人名字
int imageId = getResources().getIdentifier(item.getFaceName(),
"drawable","com.demo.v20150920");// 得到图片资源的id
viewHolder.setImageResource(R.id.face,imageId);//设置图片内容
}
})
4 结语
本文运用面向对象的编程方法设计并实现了一种Android平台下通用的Adapter类,通过一个简单通讯录列表案例,详细阐述了通用Adapter类的设计、实现以及应用过程,该Adapter类可以复用于大多数常用的显示组件(如ListView、GridView)。通过对案例代码的分析,证明使用通用Adapter类可以显著提高程序开发效率,增强程序的可扩展性和可维护性。
参考文献参考文献:
[1] 赵彦,施洋.基于Android平台的手机安全通讯录的设计与实现[J].宜宾学院学报,2014,14(12):73-77.
[2] 笪林梅.基于Android的手机通讯录管理系统的研究与实现[J].郑州轻工业学院学报:自然科学版,2013,28(3):61-64.
[3] 耿乙超.Android平台下通讯录的研究与开发[D].西安:西安电子科技大学,2012.
[4] 王国辉.Android开发实战[M].北京:清华大学出版社,2013.
[5] 倪红军,周巧扣.Android开发工程师案例教程[M].北京:北京大学出版社,2014.
(责任编辑:杜能钢)
随便看

 

科学优质学术资源、百科知识分享平台,免费提供知识科普、生活经验分享、中外学术论文、各类范文、学术文献、教学资料、学术期刊、会议、报纸、杂志、工具书等各类资源检索、在线阅读和软件app下载服务。

 

Copyright © 2004-2023 puapp.net All Rights Reserved
更新时间:2025/4/17 7:38:02