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-