微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

安卓开发笔记——打造万能适配器(Adapter) 软件设计模式之模板方法模式(JAVA)

为什么要打造万能适配器?

在安卓开发中,用到ListView和GridView的地方实在是太多了,系统认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需要,因此我们时常要去继承BaseAdapter类去实现一个自定义的适配器来满足我们的场景需要。

如果你是开发一个简单点的APP还好,可能ListView和GridView的数量不会太多,我们只要去写几个BaseAdapter实现类就可以了。

但如果有一天,你需要开发一个APP里面具有几十个ListView或者GridView的子页面,此时的你该怎么办?每个ListView或者GridView都去写一个适配的Adatper类吗?

当然你如果想做蛮牛不嫌累的话也不是不可以,但如果有办法可以让自己减少很多工作量,避免做重复无意义劳动,何乐而不为呢?

 

万能适配器思想?

软件设计模式:模板方法模式(有兴趣了解的朋友,可以参考看下我之前写过的博文《软件设计模式之模板方法模式(JAVA)》)

其实解决问题的核心思想很简单,一句话:抽取重复代码

我们在继承BaseAdapter类时,都需要去实现它里面的抽象方法getCount,getItem,getItemId,getView),其中除了getView这个方法里需要实现的代码不同,其他的都一样。

而这个getView方法里,我们考虑到性能的问题,我们经常会引入一个ViewHolder类(关于不清楚ViewHolder的朋友可以看看我之前写过的博文《安卓开发笔记——ListView加载性能优化ViewHolder》),尽可能的去节省资源。

那么解决问题的思路就出来了,我们可以把这个适配器抽取成2部分:

第一部分是解决(getCount, getItem, getItemId)方法里重复代码的问题。

第二部分是分离getView方法里使用到的ViewHolder,把它单独抽取出来成一个独立的类,利用键值对Key=>Value的方法,以控件ID去寻找对应的View对象。

 

如果你看完以上这些感觉已经云里来雾里去,没关系,接下去我们用代码说话。

 

传统适配器的实现方式:

 1 package com.example.listviewtest;
 2 
 3 import java.util.List;
 4 
 5  android.content.Context;
 6  android.view.LayoutInflater;
 7  android.view.View;
 8  android.view.ViewGroup;
 9  android.widget.BaseAdapter;
10  android.widget.ImageView;
11  android.widget.TextView;
12 
13 /**
14  * 传统适配器Adapter写法
15  * 
16  * @author Balla_兔子
17 18  */
19 public class MyAdapter extends BaseAdapter {
20     private LayoutInflater layoutInflater;
21     private List<User> data;
22 
23     public MyAdapter(Context context,List<User> data,int layoutId) {
24         //this.context = context;
25         this.layoutInflater = LayoutInflater.from(context);
26         this.data =27         this.layoutId = layoutId;
28     }
29 
30     @Override
31      getCount() {
32         return data.size();
33 34 
35 36     public Object getItem( position) {
37          data.get(position);
38 39 
40 41     long getItemId(42          position;
43 44 
45 46     public View getView( position,View convertView,ViewGroup parent) {
47         ViewHolder viewHolder = null;
48         if (convertView == ) {
49             convertView = layoutInflater.inflate(R.layout.listview_item,parent,1)">false);
50             viewHolder = new ViewHolder();
51             viewHolder.iv_image = (ImageView) convertView.findViewById(R.id.iv_image);
52             viewHolder.tv_name = (TextView) convertView.findViewById(R.id.tv_name);
53             viewHolder.tv_phone = (TextView) convertView.findViewById(R.id.tv_phone);
54             convertView.setTag(viewHolder);
55         } else {
56             viewHolder = (ViewHolder) convertView.getTag();
57         }
58 
59         User user =60         viewHolder.iv_image.setimageResource(R.drawable.ic_launcher);
61         viewHolder.tv_name.setText(user.getName());
62         viewHolder.tv_phone.setText(user.getPhone());
63 
64          convertView;
65 66 
67     private class ViewHolder {
68         ImageView iv_image;
69         TextView tv_name;
70         TextView tv_phone;
71 72 
73 }
View Code

 从上面的代码就可以感受到,如果我们去编写多个适配器Adapter的时候,那么我们就势必要去写多个ViewHolder和重复的去写(getCount,getItemId)方法,而ViewHolder里面常用的控件View也就无非那几种,而那三个方法里(getCount,getItemId)的代码也是固定不变的,所以重复代码量非常的多,我们应该把它们抽取出来。

 

万能适配器实现

 

1、首先我们先来分离这个ViewHolder,其实核心代码并没有改变,只是把传统ViewHolder给做的事情给分离出来罢了。

 4  android.util.SparseArray;
 8 
 * ViewHolder集合类
12 14   CommonViewHolder {
16     private SparseArray<View> sparseArray;
17      View convertView;
18     19 
 构造方法,完成传统Adapter里的创建convertView对象
public CommonViewHolder(Context context,1)">int layoutId,ViewGroup parent,1)">22         this.position =23         this.sparseArray = new SparseArray<View>();
this.convertView = LayoutInflater.from(context).inflate(layoutId,1)">this.convertView.setTag(this26 
27 28 
29      入口方法,完成传统Adapter里面实例化ViewHolder对象工作
30     static CommonViewHolder getCommonViewHolder(Context context,1)">31         32             return  CommonViewHolder(context,convertView,layoutId,position);
33         } 34             CommonViewHolder commonViewHolder = (CommonViewHolder) convertView.getTag();
35             特别需要注意的一点,由于ListView的复用,比如屏幕只显示5个Item,那么当下拉到第6个时会复用第1个的Item,所以这边需要更新position    
36             commonViewHolder.position =37              commonViewHolder;
39 40 
根据控件Id获取对应View对象
42     public <T extends View> T getView( viewId) {
43         View view = sparseArray.get(viewId);
44         if (view == 45             view = convertView.findViewById(viewId);
46             sparseArray.put(viewId,view);
47  (T) view;
49 50     
51     用于返回设置好的ConvertView对象
52     public View getConvertView(){
53         55 
56 }

这里我们提供了一个入口方法getCommonViewHolder来得到一个ViewHolder的实例对象,若实例不存在,我们去创建并设置Tag保存,这点和先前的ViewHolder所做的事情是一样的。

由于所有的控件都是View的子类,这里提供了一个getView来获取各控件的对象,在我们需要使用的时候强转成我们所需要的控件类型就可以了,这里提供了一个类似Map的集合SparseArray,这个类和Map一样是利用Key=>Value来存取对象的,不同的是这里的Key是整型变量。

下面是SpareseArray源码中对其的介绍:

SparseArray是Android为<Integer,Object>类型的HashMap专门写的类,目的是为了提供效率,其核心算法是折半查找,其用法和Map无两异。

 

2、再来分离下BaseAdapter,除getView这个方法会有一些不同,其他的代码其实每次书写都是一样的,我们可以自己写一个抽象类把它们都给实现了,只留getView最关键核心的代码部分给用户实现。由于方法操作,我们这里利用泛型<T>

 * 通用适配器Adapter写法
@param <T>
18 19  20 abstract class CommonAdapter<T> 为了使得子类可以访问,这里修改包访问级别
22     protected Context context;
24     protected List<T>25     protected  layoutId;
27     public CommonAdapter(Context context,List<T> data,1)">28         this.context = context;
29         30         this.layoutId =32 33 
34 35     36         37 38 
40     41         42 43 
44 45     46         48 
50     51         获取ViewHolder对象
52         CommonViewHolder myViewHolder = 需要用户复写的方法,设置所对于的View所对应的数据
        setConverView(myViewHolder,data.get(position));
55          myViewHolder.getConvertView();
56 57 
58     用户需要实现的方法
59     void setConverView(CommonViewHolder myViewHolder,T t);
60 
61 }

 

完成上面两部分的分离后,我们看看现在的适配器代码编程什么样子

 * 万能适配器Adapter写法
extends CommonAdapter<User>16 
18         super(context,data,layoutId);
20 
21 23         ((ImageView) myViewHolder.getView(R.id.iv_image)).setimageResource(R.drawable.ic_launcher);
24         ((TextView) myViewHolder.getView(R.id.tv_name)).setText(user.getName());
25         ((TextView) myViewHolder.getView(R.id.tv_phone)).setText(user.getPhone());
26 27 }

很明显,代码量减少了近2/3,而且是一劳永逸,CommonAdapter和CommonViewHolder再也不需要变动了,需要什么我们往里面直接加就可以了,这样让我们可以更为专注的去实现核心代码。当然还可以更简化一点,把这些ViewHolder.getView和setText,setimage方法再一次封装,变成只传递控件Id和对应数据就够了,这样一来我们连类都不需要写了,直接用new对象去写个内部类实现就可以了。

附上主MainActivity代码:

 java.util.ArrayList;
 5 
 android.app.Activity;
 android.os.Bundle;
 android.widget.ListView;
 9 
class MainActivity  Activity {
11     
12      ListView listView;
13      MyAdapter adapter;
14      list;
 onCreate(Bundle savedInstanceState) {
17         .onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
19         初始化
20         listView=(ListView) findViewById(R.id.listview);
21         list=new ArrayList<User>22         
23         
模拟数据源
for(int i=0;i<10;i++){
26             User user= User();
27             user.setName("用户"+i);
28             user.setPhone("10000"+29             list.add(user);
31         
32         adapter=new MyAdapter(MainActivity.,list,R.layout.listview_item);
33         
        listView.setAdapter(adapter);
35     
36 
39 }
View Code

 就像这样,以后如果需要使用适配器Adapter就不需要再去继承BaseAdapter了,直接继承CommonAdapter配合CommonViewHolder就可以了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐