一、Android适配器简介
在Android中,适配器扮演者重要的角色,是UI与Data实现绑定的一个桥梁。Adapter负责创建和显示每个项目的子View和提供对下层数据的访问。支持Adapter绑定的UI控件必须扩展AdapterView抽象类。
二、传统的ListView适配器写法
我们一向写的自定义适配器,就是继承ArrayAdapter,或者继承自BaseAdapter,然后重写4个方法,前三个方法基本相同,不同在于getView方法,getView里面为了减少绑定和View的重建,又会引入一个类ViewHolder。如图:
实现以上效果,传统的做法是这样的:
带有ListView的布局文件:
<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:
<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