标题 | 安卓平台下通用适配器类设计与实现 |
范文 | 张延年++米洪 摘 要:介绍了安卓平台下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 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 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 { View view = mViews.get(viewId); if (view == null) { view = mConvertView.findViewById(viewId); mViews.put(viewId,view); } return (T) view; } } 改进后的ViewHolder类优点:①不需要每次写一个需要显示列表的Acrivity;②通过SparseArray对象存储列表项视图组件,同时通过定义一个 使用以上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 { protected LayoutInflater mInflater;//布局填充对象 protected Context mContext; //上下文对象 protected List protected final int mItemLayoutId; //列表项视图组件资源id public CommonAdapter(Context context,List { …//省略初始化化属性代码 } } (2)此时的CommonAdapter依然是一个抽象类,除了getView方法以外我们把其它的代码都实现了。因此,在这种情况下仍然需要定义一个子类并实现getView方法,并在getView里使用前面改进过的ViewHolder类。要进一步对getView方法进行封装和抽取,关键代码如下: public abstract class CommonAdapter { … 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 { 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下载服务。