Android之打造ListView的万能适配器
一、Android适配器简介
在Android中,适配器扮演者重要的角色,是UI与Data实现绑定的一个桥梁。Adapter负责创建和显示每个项目的子View和提供对下层数据的访问。支持Adapter绑定的UI控件必须扩展AdapterView抽象类。
二、传统的ListView适配器写法
我们一向写的自定义适配器,就是继承ArrayAdapter,或者继承自BaseAdapter,然后重写4个方法,前三个方法基本相同,不同在于getView方法,getView里面为了减少绑定和View的重建,又会引入一个类ViewHolder。如图:
实现以上效果,传统的做法是这样的:
带有ListView的布局文件:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.wz.adapterdemo.MainActivity"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"/>RelativeLayout>
然后是MainActivity.java
package com.wz.adapterdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.ListView;import com.wz.adapterdemo.bean.Bean;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity { private ListView listView; private List datas; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listView); initDatas(); listView.setAdapter(new MyAdapter(this,datas)); } private void initDatas(){ datas = new ArrayList<>(); Bean bean1 = new Bean("移动开发1","Android手机开发1","2016-11-02","000-123456"); datas.add(bean1); Bean bean2 = new Bean("移动开发2","Android手机开发2","2016-11-02","000-123456"); datas.add(bean2); Bean bean3 = new Bean("移动开发3","Android手机开发3","2016-11-02","000-123456"); datas.add(bean3); Bean bean4 = new Bean("移动开发4","Android手机开发4","2016-11-02","000-123456"); datas.add(bean4); Bean bean5 = new Bean("移动开发5","Android手机开发5","2016-11-02","000-123456"); datas.add(bean5); }}
写一个bean类:
package com.wz.adapterdemo.bean;/** * Created by asus on 2016/11/2. */public class Bean { private String Title; private String desc; private String time; private String phoneNumber; public Bean(){ } public Bean(String title, String desc, String time, String phoneNumber) { Title = title; this.desc = desc; this.time = time; this.phoneNumber = phoneNumber; } public String getTitle() { return Title; } public void setTitle(String title) { Title = title; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }}
然后自定义一个MyAdapter:
package com.wz.adapterdemo;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.TextView;import com.wz.adapterdemo.bean.Bean;import java.util.List;/** * Created by asus on 2016/11/2. */public class MyAdapter extends BaseAdapter { private LayoutInflater mInflater; private List mDatas; public MyAdapter(){ } public MyAdapter(Context context, List datas) { this.mInflater = LayoutInflater.from(context); this.mDatas = datas; } @Override public int getCount() { return mDatas.size(); } @Override public Object getItem(int position) { return mDatas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if(convertView == null){ convertView = mInflater.inflate(R.layout.item_listview,parent,false); viewHolder = new ViewHolder(); viewHolder.mTitle = (TextView) convertView.findViewById(R.id.title_tv); viewHolder.mDesc = (TextView) convertView.findViewById(R.id.desc_tv); viewHolder.mTime = (TextView) convertView.findViewById(R.id.time_tv); viewHolder.mPhoneNumber = (TextView) convertView.findViewById(R.id.phone_tv); convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder) convertView.getTag(); } Bean bean = mDatas.get(position); viewHolder.mTitle.setText(bean.getTitle()); viewHolder.mDesc.setText(bean.getDesc()); viewHolder.mTime.setText(bean.getTime()); viewHolder.mPhoneNumber.setText(bean.getPhoneNumber()); return convertView; } private class ViewHolder{ TextView mTitle; TextView mDesc; TextView mTime; TextView mPhoneNumber; }}
还需要写一个item_listview.xml:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:padding="10dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <RelativeLayout android:id="@+id/rl" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/title_tv" android:textSize="18sp" android:textColor="#000000" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/desc_tv" android:textSize="15sp" android:layout_below="@id/title_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/time_tv" android:textSize="12sp" android:paddingTop="5dp" android:textColor="#890909" android:layout_below="@id/desc_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> RelativeLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentBottom="true"> <ImageView android:id="@+id/imageview" android:src="@drawable/phone" android:layout_width="20dp" android:layout_height="30dp" /> <TextView android:id="@+id/phone_tv" android:textColor="#034ef1" android:layout_toRightOf="@id/imageview" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> RelativeLayout>RelativeLayout>
如果是开发一个简单点的APP用到的ListView数量不会太多,我们只要去写几个BaseAdapter实现类就可以了。但如果有有几十个ListView,此时的你该怎么办?每个ListView都去写一个适配的Adatper类吗?当然你如果不嫌累的话也不是不可以,但如果有办法可以让自己减少很多工作量,避免做重复无意义劳动,何乐而不为呢?
三、打造ListView的万能适配器
万能适配器思想:使用模板方法设计模式其核心思想很简单:抽取重复代码!
我们在继承BaseAdapter类时,都需要去实现它里面的抽象方法(getCount, getItem, getItemId, getView),其中除了getView这个方法里需要实现的代码不同,其他的都基本一样。而这个getView方法里,我们考虑到性能的问题,我们经常会引入一个ViewHolder类,尽可能的去节省资源。那么解决问题的思路就出来了,可以把这个适配器抽取成两部分:
第一部分是解决(getCount, getItem, getItemId)方法里重复代码的问题;
第二部分是分离getView方法里使用到的ViewHolder,把它单独抽取出来成一个独立的类,利用键值对Key=>Value的方法,以控件ID去寻找对应的View对象。
仔细观察上面的传统的MyAdapter写法,getCount(), getItem(), getItemId()这三个方法都是一样,我们可以全部抽取出来。于是,可以写一个泛型使其变成一个抽象的基类,继承自BaseAdapter,然后该子类只需要去关心getView方法就行了:
package com.wz.adapterdemo;import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import java.util.List;public abstract class CommonAdapter extends BaseAdapter { private Context context; private List list; private int layoutId; public CommonAdapter(Context context, List list,int layoutId) { this.context = context; this.list = list; this.layoutId = layoutId; } @Override public int getCount() { return list == null ? 0:list.size(); } @Override public T getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ...... }}
好,实现了前面三个方法,我们再来看看getView方法,在该方法里面要先判断ViewHolder是否为空,为空则去Inflate一个xml文件进来,再绑定下视图,设置一个Tag,不为空的时候直接引用Tag。
现在,我们要把ViewHolder提取出来,但每一个item都和ViewHolder相关联,而item是需要我们自己定义的,于是,我们可以把item作为参数传入,对于item里面的控件,因为每一个控件都对应这一个id,所以可以用键值对处理,我们会先想到HashMap,但是在Android中已经为我们提供了一个性能更好的数据结构来代替HashMap,那就是SparseArray。于是,ViewHolder可以这样写:
package com.wz.adapterdemo;import android.content.Context;import android.util.SparseArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TextView;public class CommonViewHolder { private SparseArray views; private int postion; private View contertView; private Context context; /** * 构造方法,完成传统Adapter里的创建convertView对象 */ private CommonViewHolder(Context context, View contertView,ViewGroup parent, int lagoutId, int postion) { this.context = context; this.views = new SparseArray<>(); this.contertView = LayoutInflater.from(context).inflate(lagoutId,parent,false); this.postion = postion; this.contertView.setTag(this); } /** * 入口方法,完成传统Adapter里面实例化ViewHolder对象工作 */ public static CommonViewHolder getCommonViewHolder(Context context, View convertView, int layoutId, ViewGroup parent, int position){ if(convertView == null){ return new CommonViewHolder(context,convertView,parent,layoutId,position); }else { CommonViewHolder commonViewHolder = (CommonViewHolder)convertView.getTag(); /*由于ListView的复用,比如屏幕只显示5个Item,那么当下拉到第6个时会复用第1个的Item *所以这边需要更新position*/ commonViewHolder.postion = position; return commonViewHolder; } } /** * 根据控件Id获取对应View对象 */ public T getView(int viewId){ View view = views.get(viewId); if(view == null){ view = contertView.findViewById(viewId); views.put(viewId,view); } return (T)view; } /** * 返回设置好的ConvertView对象 */ public View getContertView(){ return contertView; } /** *给TextView设置字符串 */ public CommonViewHolder setText(int viewId,String text){ TextView textView = getView(viewId); textView.setText(text); return this; } /** *给ImageView设置图片资源 */ public CommonViewHolder setImageResource(int viewId,int drawableId){ ImageView imageView = getView(viewId); imageView.setImageResource(drawableId); return this; }}
这里我们提供了一个入口方法getCommonViewHolder来得到一个ViewHolder的实例对象,若实例不存在,我们去创建并设置Tag保存,这点和先前的ViewHolder所做的事情是一样的。由于所有的控件都是View的子类,这里提供了一个getView来获取各控件的对象,在我们需要使用的时候强转成我们所需要的控件类型就可以了。
于是,我们CommonAdapter中的getView方法可以这么写:
@Override public View getView(int position, View convertView, ViewGroup parent) { CommonViewHolder commonViewHolder = CommonViewHolder.getCommonViewHolder(context,convertView,layoutId,parent,position); convert(commonViewHolder,getItem(position)); return commonViewHolder.getContertView(); } public abstract void convert(CommonViewHolder holder,T item);
这里提供一个抽象方法convert,在调用的地方用户自己实现。
最后我们来看主方法的调用(用户根据需要重载convert方法):
package com.wz.adapterdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.ListView;import com.wz.adapterdemo.bean.Bean;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity { private ListView listView; private List datas; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listView); initDatas(); // listView.setAdapter(new MyAdapter(this,datas)); listView.setAdapter(new CommonAdapter(this,datas,R.layout.item_listview) { @Override public void convert(CommonViewHolder holder, Bean item) { holder.setText(R.id.title_tv,item.getTitle()) .setText(R.id.desc_tv,item.getDesc()) .setText(R.id.time_tv,item.getTime()) .setText(R.id.phone_tv,item.getPhoneNumber()) .setImageResource(R.id.imageview,R.drawable.phone); } }); } private void initDatas(){ datas = new ArrayList<>(); Bean bean1 = new Bean("移动开发1","Android手机开发1","2016-11-02","000-123456"); datas.add(bean1); Bean bean2 = new Bean("移动开发2","Android手机开发2","2016-11-02","000-123456"); datas.add(bean2); Bean bean3 = new Bean("移动开发3","Android手机开发3","2016-11-02","000-123456"); datas.add(bean3); Bean bean4 = new Bean("移动开发4","Android手机开发4","2016-11-02","000-123456"); datas.add(bean4); Bean bean5 = new Bean("移动开发5","Android手机开发5","2016-11-02","000-123456"); datas.add(bean5); }}
很明显,代码量减少很多,CommonAdapter和CommonViewHolder再也不需要修改,需要什么我们往里面直接加就可以了,这样让我们可以更为专注的去实现核心代码。当然还可以更简化一点,把这些ViewHolder.getView和setText,setImage等方法再一次封装,变成只传递控件Id和对应数据就够了,这样一来我们连类都不需要写了。于是,我们可以再定义一个Adapter继承CommonAdapter,在里面实现控件内容的设定:
package com.wz.adapterdemo;import android.content.Context;import com.wz.adapterdemo.bean.Bean;import java.util.List;public class MyCommonAdapter extends CommonAdapter { public MyCommonAdapter(Context context, List list, int layoutId) { super(context, list, layoutId); } @Override public void convert(CommonViewHolder holder, Bean item) { holder.setText(R.id.title_tv,item.getTitle()) .setText(R.id.desc_tv,item.getDesc()) .setText(R.id.time_tv,item.getTime()) .setText(R.id.phone_tv,item.getPhoneNumber()) .setImageResource(R.id.imageview,R.drawable.phone); }}
在MainActivity中setAdapter:
listView.setAdapter(new MyCommonAdapter(this,datas,R.layout.item_listview));
这样,以后如果需要使用适配器Adapter就不需要再去继承BaseAdapter了,直接继承CommonAdapter配合CommonViewHolder就可以了。
项目案例源码下载地址:
http://download.csdn.net/detail/wei_zhi/9671511
更多相关文章
- 《Android经验分享》周刊第4期
- 关于android中使用new Message的内存泄露问题
- Android(安卓)开发 调用图库选择图片实现和参数详解
- android 自定义控件学习之三 控件布局常用知识总结
- AndFix解析——(上)
- 如何编程实现开启或者关闭GPS
- Android音乐播放器开发
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用