ListView ,GridView 通用适配器

前言


  接近半年的时间没有写博客了,今年公司的项目有点多,比较忙,没时间写,这是其一。其次是,这半年来,有时间的时候,
我都会看看自己以前写的博客,也许是以前刚刚写博客,经验不足,感觉写出来的博客质量很不好,而最近,也经常在网上看别人的博客
学到到了很多,趁最近项目都差不多收尾了,就写写今年下半年的第一篇博客...

  从事Android开发工作有几年时间了,我发现做手机应用的话,基本都离不开ListView,GridView,这些控件,尤其是ListView
我曾开发过一个项目,70%的都是列表,光写adapter,就接近上百个,写到最后,都感觉已经麻木了...比如下面这个例子

MainActivity页面

 1 package com.example.huangjialin.listviewadapter;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.widget.ListView;
 6 
 7 import java.util.ArrayList;
 8 import java.util.List;
 9 
10 public class MainActivity extends Activity {
11     private ListView listview;
12     private List<String> stringList;
13     private TestAdapter adapter;
14 
15 
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         setContentView(R.layout.activity_main);
20 
21         stringList = new ArrayList<String>();
22         for (int i = 0; i < 50; i++) {
23             stringList.add("测试" + i);
24         }
25 
26         listview = (ListView) findViewById(R.id.listview);
27         adapter = new TestAdapter(this, stringList);
28         listview.setAdapter(adapter);
29     }
30 }

主页面布局

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:id="@+id/activity_main"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent"
 6     >
 7 
 8     <ListView
 9         android:id="@+id/listview"
10         android:layout_width="match_parent"
11         android:layout_height="match_parent"
12         />
13 </RelativeLayout>

TestAdapter页面

 1 package com.example.huangjialin.listviewadapter;
 2 import android.content.Context;
 3 import android.view.LayoutInflater;
 4 import android.view.View;
 5 import android.view.ViewGroup;
 6 import android.widget.BaseAdapter;
 7 import android.widget.TextView;
 8 import java.util.List;
 9 
10 /**
11  * Created by huangjialin on 2017/9/12.
12  */
13 public class TestAdapter extends BaseAdapter {
14     private List<String> stringList;
15     private Context mContext;
16 
17     public TestAdapter(Context context, List<String> stringList) {
18         this.stringList = stringList;
19         this.mContext = context;
20     }
21 
22     @Override
23     public int getCount() {
24         return stringList.size();
25     }
26 
27     @Override
28     public Object getItem(int position) {
29         return null;
30     }
31 
32     @Override
33     public long getItemId(int position) {
34         return 0;
35     }
36 
37     @Override
38     public View getView(int position, View convertView, ViewGroup parent) {
39         ViewHolder viewHolder = null;
40         if (convertView == null) {
41             convertView = LayoutInflater.from(mContext).inflate(R.layout.item_single_str,null);
42             viewHolder = new ViewHolder();
43             viewHolder.mTextView = (TextView) convertView
44                     .findViewById(R.id.id_tv_title);
45             convertView.setTag(viewHolder);
46         } else {
47             viewHolder = (ViewHolder) convertView.getTag();
48         }
49         viewHolder.mTextView.setText(stringList.get(position));
50         return convertView;
51     }
52 
53     private final class ViewHolder {
54         TextView mTextView;
55     }
56 }

item_single_str  Item布局

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical">
 6 
 7     <TextView xmlns:android="http://schemas.android.com/apk/res/android"
 8         android:id="@+id/id_tv_title"
 9         android:layout_width="match_parent"
10         android:layout_height="50dp"
11         android:background="#aa111111"
12         android:gravity="center_vertical"
13         android:paddingLeft="15dp"
14         android:text="hello"
15         android:textColor="#ffffff"
16         android:textSize="20sp"
17         android:textStyle="bold"/>
18 </LinearLayout>

是不是觉得上面的代码很熟悉,写了很多,甚至都写到吐了,但是如果仔细观察的话,就会发现adapter里面的代码很多都是固定的
那么我们能不能把那些重复的代码封装起来呢...

ViewHolder

先简单的说一下ViewHolder的作用,可以理解为是一个容器,每一个convertView通过setTag来绑定一个ViewHolder对象,convertView中的控件就保存到ViewHolder中,当convertView复用的时候,直接通过getTag从ViewHolder中
取出来即可,不在需要在到布局文件中通过findViewById去找。通常,Android中写一个列表,无论是ListView还是GridView,我们都是一个activity对应一个adapter,但是如果我们能写出一个通用的adapter的话,是不是会省事很多呢??
先不说其他的,至少我们的代码会少很多。好,come on ,接着往下。。。

万能ViewHolder

从ViewHolder下手,先上代码

 1 package com.example.administrator.listviewtest;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 
 7 import java.util.HashMap;
 8 import java.util.Map;
 9 
10 /**
11  * Created by Administrator on 2017/10/10 0010.
12  */
13 
14 public class ViewHolder {
15     private View mConvertView;
16     private Map<Integer,View> viewMap; //保存控件
17 
18 
19 
20     public ViewHolder(Context context,int layoutId) {
21         viewMap = new HashMap<Integer,View>();
22         mConvertView = LayoutInflater.from(context).inflate(layoutId,null);
23         mConvertView.setTag(this);
24     }
25 
26 
27     /**
28      * 获取ViewHolder对象
29      * @param convertView
30      * @return
31      */
32     public static ViewHolder getViewHolder(View convertView,Context context,int layoutId) {
33         //先判断convertView组件是否存在,存在的话,说明ViewHolder已经创建
34         if (convertView == null) {
35             return new ViewHolder(context,layoutId);
36         }
37         return (ViewHolder) convertView.getTag();
38     }
39 
40     /**
41      * 获取控件
42      * 由于每一个item布局不一样,控件是未知的,但是,无论是哪一个控件,他的父类都是View
43      */
44     public <T extends View > T getView(int viewId){
45         View view = viewMap.get(viewId); //从Map中去出控件
46         if(view == null){ //说明,没有有这个控件,到布局中找
47             view = mConvertView.findViewById(viewId);
48             viewMap.put(viewId,view);
49         }
50         return (T) view;
51     }
52 
53     public View getConvertView(){
54         return mConvertView;
55     }
56 }

ViewHolder中,我创建了一个Map,用来保存控件,当然你也可以使用其他,比如SparseArray,ArrayMap,相比效率或者性能
方面,后者会比HashMap好很多,只是我觉得使用HashMap,更多人会更容易理解,如果想使用后两种,那直接替换就可以了,
他们的区别我在这里就不说了,有兴趣的朋友可以看看这篇文章:http://blog.csdn.net/u010687392/article/details/47809295

现在再来看看适配器中的代码

TestAdapter类

 1 .
 2     .省略若干方法
 3     .
 4     
 5      @Override
 6     public View getView(int position, View convertView, ViewGroup parent) {
 7       /* 
 8         常规的写法
 9       ViewHolder viewHolder;
10         if (convertView == null) {
11             convertView = LayoutInflater.from(mContext).inflate(R.layout.item_single_str, null);
12             viewHolder = new ViewHolder();
13             viewHolder.mTextView = (TextView) convertView .findViewById(R.id.id_tv_title);
14             convertView.setTag(viewHolder);
15         } else {
16             viewHolder = (ViewHolder) convertView.getTag();
17         }
18         viewHolder.mTextView.setText(stringList.get(position));
19         return convertView;*/
20 
21         
22         //使用万能的ViewHoler协防
23         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
24         TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
25         textView.setText(stringList.get(position));
26         return holder.getConvertView();
27 
28 
29     }
30 
31    /* private final class ViewHolder {
32         TextView mTextView;
33     }*/

简单的说一下思路:
先看TestAdapter中的getView方法,先获取这个ViewHolder对象,而获取ViewHolder 的时候,先判断convertView是否存在,如果存在,则说明ViewHolder对象已经存在,直接通过getTag来获取,如果不存在,则需要将item的布局填充到
convertView中,再通过setTag进行绑定。其实和常规写法的思路差不多,唯独就是每一次创建新的一个ViewHolder的时候,会创建一个viewMap,这个viewMap是用来保存控件的。然后在通过viewholder对象从viewMap中取出相应的控件
从而进行赋值。这里需要注意一个就是,getView方法中return 的view,是在ViewHolder填充Item的mConvertView,而不是直接拿getView中的convertView ......哈哈是不是代码量少很多了啊,现在不在需要每次创建一个adapter都要弄一个ViewHolder了
省事很多,但是......这里只是刚刚开始而已,接着开干....

万能的adapter适配器:
前面我们弄了一个通用ViewHolder,但是,我们写出来的列表还是得创建一个adapter,类并没有少,代码量少了一点点,这并不能达到我想要的,我想要的是类也少,代码量也少,万能adapter出来吧...动画片看多了
在封装一个万成的适配器之前,我们先看一下这个adapter的代码:

 1 package com.example.administrator.listviewtest;
 2 
 3 import android.content.Context;
 4 import android.view.View;
 5 import android.view.ViewGroup;
 6 import android.widget.BaseAdapter;
 7 import android.widget.TextView;
 8 
 9 import java.util.List;
10 
11 
12 /**
13  * Created by huangjialin on 2017/9/12.
14  */
15 public class TestAdapter extends BaseAdapter {
16     private List<String> stringList;
17     private Context mContext;
18 
19     public TestAdapter(Context context, List<String> stringList) {
20         this.stringList = stringList;
21         this.mContext = context;
22     }
23 
24     @Override
25     public int getCount() {
26         return stringList.size();
27     }
28 
29     @Override
30     public Object getItem(int position) {
31         return stringList.get(position);
32     }
33 
34     @Override
35     public long getItemId(int position) {
36         return position;
37     }
38 
39     @Override
40     public View getView(int position, View convertView, ViewGroup parent) {
41         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
42         TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
43         textView.setText(stringList.get(position));
44         return holder.getConvertView();
45     }
46 }

从代码中,我们可以看到,继承BaseAdapter,实现这4个方法,其中有三个方法写法基本是固定不变的最主要的就是getView方法了,既然我们要弄一个万能适配器,是不是只要把getView方法单独抽出来实现就好,对吧。还有既然是通用的adapter,
那么数据肯定是不固定的,数据,我们就得需要泛型了....come on ,先创建一个万能适配器的类 PowerfulAdapter

 1 package com.example.administrator.listviewtest;
 2 
 3 import android.content.Context;
 4 import android.widget.BaseAdapter;
 5 import java.util.List;
 6 
 7 /**
 8  * Created by Administrator on 2017/10/11 0011.
 9  * 万能适配器
10  */
11 
12 public abstract class PowerfulAdapter<T> extends BaseAdapter {
13 
14     private List<T> stringList;
15     private Context mContext;
16 
17     public PowerfulAdapter(List<T> stringList, Context mContext) {
18         this.stringList = stringList;
19         this.mContext = mContext;
20     }
21 
22     @Override
23     public int getCount() {
24         return stringList.size();
25     }
26 
27     @Override
28     public Object getItem(int position) {
29         return stringList.get(position);
30     }
31 
32     @Override
33     public long getItemId(int position) {
34         return position;
35     }
36 }

从代码中我们可以看到,除了这个getView方法我们没有实现之外,另外的三个方法,我们都实现了,并且把这个类弄成抽象的,由于这几个方法,基本写法都是固定的,所以后面我们写适配器的时候,只需要继承这个万成的适配器,并且实现getView一个方法就可以了
,另外几个方法我们就不在需要考虑了。为了和前面的TestAdapter区别开来,我们另外创建一个适配器,SecondAdapter这个适配器就继承我们的万能适配器PowerfulAdapter<T>,上代码

 1 package com.example.administrator.listviewtest;
 2 
 3 import android.content.Context;
 4 import android.view.View;
 5 import android.view.ViewGroup;
 6 import android.widget.TextView;
 7 
 8 import java.util.List;
 9 
10 /**
11  * Created by Administrator on 2017/10/11 0011.
12  */
13 
14 public class SecondAdapter<T> extends PowerfulAdapter<T>{
15     private List<String> stringList;
16     private Context mContext;
17 
18     public SecondAdapter(List<T> stringList, Context mContext) {
19         super(stringList);
20         this.stringList = (List<String>) stringList;
21         this.mContext = mContext;
22     }
23 
24     @Override
25     public View getView(int position, View convertView, ViewGroup viewGroup) {
26         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
27         TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
28         textView.setText(stringList.get(position));
29         return holder.getConvertView();
30     }
31 }

现在我们的适配器,继承这个PowerfulAdapter<T>这个万能适配器,最后面只需要实现一个getView方法就完全可以了,相当省了一般的代码量,有些兄弟就说了,毛线啊,我从头看到尾,我没发现代码少啊,我见你反而多写了几个类呢,
莫慌,现在我这边只是写了一个列表,,我写了ViewHolder,PowerfulAdapter<T> ,而且这两个类相当于工具类一样,以后所有的listview或者gridview都可以使用,这样效率就会高的多了....实际上封装到这,也差不多可以了,也满足了大部分人的需要了,
但是,我这么帅,得省点时间出来约会啊,还是觉得代码太多了,接着封装...

我们观察getView方法会发现

1 TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
2 textView.setText(stringList.get(position));

这些TextView是Android常用的控件,那如果我们能不能先把一些常用的控件先封装起来,在这里就不需要获取了呢,我们试试。。。

我们在ViewHolder类中加一个这样的方法

 1 /**
 2      * 给TextView设置值
 3      */
 4     public ViewHolder setText(int viewId, String text) {
 5         if (viewId > 0 && text != null) {
 6             TextView tv_Text = getView(viewId);
 7             tv_Text.setText(text);
 8         }
 9         return this;
10     }

然后在getView方法中只需要这样写...

 1   @Override
 2     public View getView(int position, View convertView, ViewGroup viewGroup) {
 3         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
 4 
 5         /**
 6          * 旧的写法
 7          */
 8         /*TextView textView = holder.getView(R.id.id_tv_title);//从ViewHolder中获取控件
 9         textView.setText(stringList.get(position));*/
10 
11         /**
12          * 在ViewHolder加了setText方法后的写法
13          */
14         holder.setText(R.id.id_tv_title, stringList.get(position));
15 
16 
17         return holder.getConvertView();
18     }

哈哈哈,是不是代码又比原来少了一点,但是,在ViewHolder中加一些常用控件得自己手动添加,可能刚刚开始会觉得很不全面,当久而久之,加进去多了,就慢慢完善了。
到这里,基本上一种封装已经完成了。到时候我会将这一部分源码上传,需要的可以下载... 可以在文章后面下载,也可以点击这里 下载

But,有些人就会说,你前面不是说能够少创建很多Java文件吗,我没有看到在哪里少呢,莫慌,马上来了...

在回到 PowerfulAdapter<T>这个类,前面我们并没有在这个类中实现getView方法,但是现在我们在该类中实现getView方法,附上代码

 1 package com.example.administrator.listviewtest;
 2 
 3 import android.content.Context;
 4 import android.view.View;
 5 import android.view.ViewGroup;
 6 import android.widget.BaseAdapter;
 7 import java.util.List;
 8 
 9 /**
10  * Created by Administrator on 2017/10/11 0011.
11  * 万能适配器
12  */
13 
14 public abstract class PowerfulAdapter<T> extends BaseAdapter {
15     private Context mContext;
16     private List<T> stringList;
17 
18 
19     public PowerfulAdapter(Context mContext, List<T> stringList) {
20         this.mContext = mContext;
21         this.stringList = stringList;
22     }
23 
24     @Override
25     public int getCount() {
26         return stringList.size();
27     }
28 
29     @Override
30     public Object getItem(int position) {
31         return stringList.get(position);
32     }
33 
34     @Override
35     public long getItemId(int position) {
36         return position;
37     }
38 
39     @Override
40     public View getView(int position, View convertView, ViewGroup viewGroup) {
41         ViewHolder holder = ViewHolder.getViewHolder(convertView, mContext, R.layout.item_single_str); //获取ViewHolder对象
42         convert(holder,stringList.get(position));
43         return holder.getConvertView();
44     }
45 
46     /**
47      * 对外提供一个抽象方法
48      */
49     public abstract void convert(ViewHolder helper, T item);
50 
51 }

在MainActivity的直接一个匿名内部类来实现convert方法,附上代码

 1 package com.example.administrator.listviewtest;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.view.View;
 6 import android.widget.AdapterView;
 7 import android.widget.Button;
 8 import android.widget.ListView;
 9 import android.widget.Toast;
10 
11 import java.util.ArrayList;
12 import java.util.List;
13 
14 public class MainActivity extends Activity {
15     private ListView listview;
16     private List<String> stringList;
17 
18     private PowerfulAdapter adapter;
19 
20 
21     @Override
22     protected void onCreate(Bundle savedInstanceState) {
23         super.onCreate(savedInstanceState);
24         setContentView(R.layout.activity_main);
25 
26         stringList = new ArrayList<String>();
27         for (int i = 0; i < 50; i++) {
28             stringList.add("不写adapter,直接匿名内部类实现" + (i + 50));
29         }
30 
31         listview = (ListView) findViewById(R.id.listview);
32 
33 
34         adapter = new PowerfulAdapter<String>(this, stringList, R.layout.item_single_str) {
35             @Override
36             public void convert(ViewHolder holder, String item) {
37                 holder.setText(R.id.id_tv_title, item);
38                 Button button = holder.getView(R.id.button);
39 
40                 button.setOnClickListener(new View.OnClickListener() {
41                     @Override
42                     public void onClick(View view) {
43                         Toast.makeText(MainActivity.this, "我是按钮,我被点击了----->", Toast.LENGTH_LONG).show();
44                     }
45                 });
46 
47             }
48         };
49         listview.setAdapter(adapter);
50 
51         listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
52             @Override
53             public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
54                 Toast.makeText(MainActivity.this, "我被点击了----->" + i, Toast.LENGTH_LONG).show();
55             }
56         });
57 
58     }
59 
60 
61 }

最后附上一张运行的效果图

ok,是不是连adapter都不需要创建了,如果项目有几十,几百个列表,就相当于少了几十几百个Java类,多爽啊,哈哈最后附上两种封装方式的源码链接,
有需要的朋友可以下载,同时,如果有朋友发现bug或者将已解决问题的方法,或者有更好的封装方式,欢迎留言,一块探讨学习。

源码链接

通用适配器封装源码:https://github.com/343661629/---ListView-GridView

另一种封装方式源码: https://github.com/343661629/ListView-

原文地址:https://www.cnblogs.com/huangjialin/p/7661328.html