基于Android平台的通用Adapter适配器的设计与实现
2016-06-16武伟
武伟
摘要:Android开发中,ListView列表是使用率最高的UI组件,传统开发模式中,ListView的代码编写比较繁琐,项目中充斥着大量的Adapter适配器,该文在分析ListView开发原理的基础上,设计并实现一个可以通用于各种ListView场景开发的Adapter适配器,使得无论多少ListView,只需一个Adapter适配器,从而解决项目开发中代码高冗余、高复杂、管理麻烦等实际问题。
关键词:Android;ListView;Adapter;适配器
中图分类号:TP393 文献标识码:A 文章编号:1009-3044(2016)10-0099-03
移动开发方兴未艾,如火如荼,各种App使我们的生活和工作变得快捷方便,Android平台作为两大移动操作系统之一,占据了移动开发的大半壁江山。
在Android App的开发实践中,ListView列表是使用最为频繁的UI组件,也是最为重要的内容呈现方式,几乎所有的App中都使用了数量不等的各种列表。众所周知,Android开发采用经典的MVC模式,因为ListView的这种重要性和普适性,Android系统在设计上使用MVC对其进行了精心的设计,一个ListView由三部分组成:
数据源:显示在界面上的具体数据,由数组或数据库等提供,对应于MVC中的M(模型)
Adapter适配器:将数据绑定到显示组件,并提供对数据的操作,对应于MVC中的C(控制器)
ListView组件:显示数据,响应用户输入,对应于MVC中的V(视图)
三者的关系如下图:
这种模式中,Adapter适配器作为数据和显示之间的桥梁,是最为关键的一部分,每当需要显示列表中的某一项时,都会调用Adapter的getView方法返回一个View。在具体的开发中,对于每一个列表页面,我们通常要编写4个文件:
1)Activity的布局文件
2) Activity的类文件
3)ListView中Item的布局文件
4) Adapter的类文件
当项目比较简单时,这种设计有利于视图层和业务层分离解耦,从而提高灵活性和复用性,但是当项目中列表页面比较多时,每一个列表都需要一个Adapter文件,很容易导致源文件数量比较多,同时大量的Adapter文件中包含了很多相似代码,从而使得代码管理的复杂度和冗余度增大,进而大大降低这种设计所带来的好处。如果可以设计实现一个通用的Adapter可以适配各种不同的列表,则可以将这种副作用降到最低,从而提高我们的代码质量。
考虑以下几种常见的列表页面:
列表1的Item包含2行文字,列表2的Item包含一个图片和2行文字,其布局不同,Adapter的处理也不尽相同,为了适用各种不同的布局和数据,我们可以使用Java的泛型技术来实现,下面是Adapter的实现代码:
1
2 public abstract class CommonAdapter
3 {
4 protected Context iContext;// 上下文
5 protected List
6 private int iLayoutId;// 列表Item的布局id
7
8 public CommonAdapter( Context aContext, List
9 {
10 iContext = aContext;
11 iListData = aListData;
12 iLayoutId = aLayoutId;
13 }
14
15 @Override
16 public int getCount()
17 {
18 return iListData.size();
19 }
20
21 @Override
22 public T getItem(int position)
23 {
24 return iListData.get(position);
25 }
26
27 @Override
28 public long getItemId(int position)
29 {
30 return position;
31 }
32
33 @Override
34 public View getView( int position, View convertView, ViewGroup parent )
35 {
36 ViewHolder holder = ViewHolder.get( iContext, convertView, parent, 37 iLayoutId, position 1);
38 convert( holder, getItem(position) );
39 return holder.getConvertView();
40 }
41
42 public abstract void convert( ViewHolder holder, T t );
43 }
这段代码与我们平时所写的Adapter十分相似,主要有以下区别:
1) 行2,该类声明为抽象类,并使用
2) 行5和行8,列表数据使用
3) 行34的getView()方法与平时有所区别,这里使用了自定义的ViewHolder类来返回每一行的视图,一方面可以优化显示效率,另一方面通过该类完成通用适配的主要内容;
4) 行42,定义了一个convert() 抽象方法,我们在使用通用Adapter进行具体开发时,必须通过重载此方法来设置Item的数据。
CommonAdapter中使用的ViewHolder是实现通用Adapter的重点部分,其代码如下:
1 public class ViewHolder
2 {
3 private SparseArray
4 private int iPosition;
5 private View iConvertView;
6 private Context iContext;
7 private int iLayoutId;
8
9 public ViewHolder( Context aContext, ViewGroup aParent, int aLayoutId, int
10 aPosition )
11 {
12 iContext = aContext;
13 iLayoutId = aLayoutId;
14 iPosition = aPosition;
15 iViews = new SparseArray
16 iConvertView = LayoutInflater.from( aContext).inflate(aLayoutId, aParent,
17 false );
18 iConvertView.setTag(this);
19 }
20
21 public static ViewHolder get( Context aContext, View convertView, ViewGroup
22 aParent, int aLayoutId, int aPosition )
23 {
24 if (convertView == null)
25 return new ViewHolder( aContext, aParent, aLayoutId, aPosition );
26 else{
27 ViewHolder holder = (ViewHolder)convertView.getTag();
28 holder.iPosition = aPosition;
29 return holder;
30 }
31 }
32
33 public
34 {
35 View view = iViews.get( aViewId );
36 if (view == null)
37 {
38 view = iConvertView.findViewById( aViewId );
39 iViews.put( aViewId, view );
40 }
41 return (T) view;
42 }
43
44 public View getConvertView()
45 {
46 return iConvertView;
47 }
48
49 public ViewHolder setText( int aViewId, String aText )
50 {
51 TextView tv = getView( aViewId );
52 tv.setText( aText );
53 return this;
54 }
55
56 public ViewHolder setImageDrawable( int aViewId, Drawable aDrawable )
57 {
58 ImageView view = getView( aViewId );
59 view.setImageDrawable( aDrawable );
60 return this;
61 }
62 }
其中重点的部分是:
1) 行3,使用稀疏数组保存View,因为实际使用时各种列表的Item布局是不同的,为了达到通用的目的,我们需要一个容器来保存Item布局中的所有控件,并且可以通过控件Id找到对应控件,这个容器也可以使用Map,不过出于性能考虑,这里使用稀疏数组;
2) 行18,使用setTag()方法将convertView与ViewHolder对象进行绑定,当返回convertView时,使用getTag()方法拿到布局中的控件,从而提高效率;
3) 行33,getView()方法是ViewHolder的重点方法,同样使用
4) 行49开始的setText()和setImageDrawable()方法:实践中,绝大多数情况下,列表Item的布局中所使用到的控件,不过就是文本、图片、按钮、单选/复选等几种,所以我们可以在ViewHolder中将设置文本/图片/属性/事件处理等功能进行封装,限于篇幅,这里只封装了设置文本和图片,读者可根据需要自行添加其他功能。
有了CommonAdapter和ViewHolder这2个类,一个通用的Adapter适配器就可以完成了,无论项目中使用了多少列表,我们也无需几十个Adapter满天飞了;使用该通用Adapter来完成前例中“列表2”的具体代码如下:
1 protected void onCreate(Bundle savedInstanceState) {
2 super.onCreate(savedInstanceState);
3 setContentView(R.layout.activity_list2);
4
5 InitListData(); // 初始化列表数据,可静态赋值或数据库读取
6 listView = (ListView)findViewById( R.id.list2 );
7 listView.setAdapter( new CommonAdapter
8 getApplicationContext(), listData, R.layout.layout_item2 )
9 {
10 @Override
11 public void convert( ViewHolder helper, List2Node item )
12 {
13 helper.setText( R.id.tv_name, item.line1 );
14 helper.setText( R.id.tv_ename, item.line2 );
15 helper.setImageDrawable( R.id.image, item.image );
16 }
17 });
18 }
我们只需要根据Item布局重载convert()方法并设置数据,代码简洁明了,神清气爽!
通过以上的实践,我们利用泛型技术结合面向对象思想,设计实现了一个可用于各种列表的通用Adapter,并在实际项目中成功地应用,使用该技术,可大大减少代码冗余,提高代码复用性,增强代码质量,降低项目管理的难度,具有较强的实用性。
参考文献:
[1] Android Developers[EB/Ol].http://developer.android.com.
[2] Bruce Eckel. Java编程思想[M]. 4版.北京: 机械工业出版社, 2007.
[3] 邓文渊. Android开发基础教程[M]. 北京: 人民邮电出版社, 2014.
[4] Bill Phillips, Brian Hardy. Android编程权威指南[M]. 北京: 人民邮电出版社, 2014.
[5] 李继勇. Android UI设计[M]. 北京: 机械工业出版社, 2015.