Android教你如何一步步打造通用适配器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/IT_ZJYANG/article/details/52081730

前言

在Android开发中ListView是最为常用的控件之一,基本每个应用都会涉及到它,要使用ListView列表展示,就不可避免地涉及到另外一个东西——Adapter,我们都知道,Adapter是连接数据和列表界面的一个桥梁,一般项目中一个listview就会有一个Adapter与之对应,然后就是一堆方法的重写,包括getCount,getItem,getView等等,遇到自定义布局时还需重写getView方法,重写getView的时候逻辑不复杂还好,遇到代码逻辑复杂的时候adapter简直臃肿,并且还需要写很多次重复的代码,比如判断convertView是否为空,findViewById无数次停不下来睡觉



写了这么多,你是否想过,可否有一个公用的自定义Adapter基类,将这些经常重复的代码和逻辑封装起来,方便我们调用,减少getView中的代码逻辑,下面就来一步步将其“包装”起来成为我们想要的效果。


先走一遍我们之前写ListView和Adapter的方式:

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <ListView 
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
    </ListView>

</RelativeLayout>



MainActivity:

public class MainActivity extends Activity {
	
	private ListView listview;
	
	private MyAdapter adapter;
	
	private List<String> data;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		listview = (ListView)this.findViewById(R.id.listview);
		
		getData();
		
		adapter = new MyAdapter(this.getApplicationContext(), data);
		
		listview.setAdapter(adapter);
	}
	
	public void getData(){
		data = new ArrayList<String>();
		for(int i=0; i<20; i++){
			data.add("数据"+i);
		}
	}
}



自定义适配器 MyAdapter:

public class MyAdapter extends BaseAdapter{
	
	private Context mContext;
	
	private List<String> list;
	
	public MyAdapter(Context context, List<String> list){
		this.mContext = context;
		this.list = list;
	}
	
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}
	
	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		ViewHolder viewholder = null;
		if(convertView == null){
			convertView = View.inflate(mContext, R.layout.item_listview, null);
			viewholder = new ViewHolder();
			viewholder.titleTv = (TextView)convertView.findViewById(R.id.titleTv);
			convertView.setTag(viewholder);
		}
		else{
			viewholder = (ViewHolder)convertView.getTag();
		}
		viewholder.titleTv.setText(list.get(position));
		
		return convertView;
	}
	
	public static class ViewHolder{
		TextView titleTv;
	}

}



每个列表项的布局文件 item_listview.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/titleTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:paddingLeft="15dp"
        android:textSize="20sp"
        android:textColor="#000"
        />
    
</LinearLayout>



对于以上这部分代码不太明白可以参考我之前的文章ListView基础篇ListView优化篇,通过以上代码就可以完成一个简单的列表界面和数据展示:




接下来我们就在这个基础上进行一步步封装:

可以看到,我们每次调用getView时候,都会新建一个ViewHolder,然后判断convertView是否为空,以及用

viewholder存储我们每个列表项的子控件,再通过setTag和getTag来复用Viewholder,这一部分逻辑是每次getView

都会调用的,所以首先能够想到将ViewHolder对象的逻辑给封装起来,封装ViewHolder首先要考虑以下几点:

封装ViewHolder

1.首先这个封装来肯定要有一个ViewHolder的构造方法,另外,从上面可以看出每次getView都需要初始化convertView,那么我们可以将convertView的初始化搬到ViewHold的构造方法中来进行,既然convertView要在ViewHolder的构造方法中初始化,那么必定还需要inflate所需要的参数,以及每一个Item的下标,即context、layoutId、ViewGroup、position:

public class CommonViewHolder {
	
	public View mConvertView;

	public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
		mConvertView = View.inflate(context, layoutId, null);
		mConvertView.setTag(this);
	}
}



2.注意到以前的方式每次都需要判断convertView是否为null,是则new一个新的convertView和ViewHolder实例并且setTag,否则采用getTag重用之前的ViewHolder:

public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
	if(convertView == null){
		return new CommonViewHolder(context, position, layoutId, parent);
	}
	else{
		return (CommonViewHolder)convertView.getTag();
	}
}



3.不同场景下列表项的元素是不确定的,数量和类型都不一致,既然是打造通用Adapter,那肯定要兼容多种情况,数量上我们可以想到使用Map来存储我们的子控件,类型上可以使用Java的泛型来构造,如下:

private HashMap<Integer, View> map = new HashMap<Integer, View>();

public <T extends View> T getView(int viewId){
	View view = map.get(viewId);
	//如果view为空,则findId找到,并放进map中
	if(view == null){
		view = mConvertView.findViewById(viewId);
		map.put(viewId, view);
	}
	//如果view不会空,则直接返回
	return (T)view;
}



4.当然,以前我们getView方法最后返回的是一个convertView,所以还可以提供一个getConvertView的方法返回每一行对应的convertView:

public View getConvertView(){
	return mConvertView;
}



至此,完整的通用ViewHolder已打造完毕,完整代码如下:

public class CommonViewHolder {
	
	public HashMap<Integer, View> map;
	
	public View mConvertView;
	
	public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
		map = new HashMap<Integer, View>();
		mConvertView = View.inflate(context, layoutId, null);
		mConvertView.setTag(this);
	}
	
	public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
		if(convertView == null){
			return new CommonViewHolder(context, position, layoutId, parent);
		}
		else{
			return (CommonViewHolder)convertView.getTag();
		}
	}
	
	public <T extends View> T getView(int viewId){
		View view = map.get(viewId);
		if(view == null){
			view = mConvertView.findViewById(viewId);
			map.put(viewId, view);
		}
		return (T)view;
	}

	public View getConvertView(){
		return mConvertView;
	}
}



如今,我们Adapter中的getView也就变成了这个样子:

public View getView(int position, View convertView, ViewGroup parent) {
	// TODO Auto-generated method stub
	CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
	TextView titleTv = holder.getView(R.id.titleTv);
	titleTv.setText(list.get(position));
	return holder.getConvertView();
}



它的代码运行流程如下:
1.首先进入get方法获取一个ViewHolder实例,如果convertView为空,则进入到构造方法,new一个用来存放这一行的map集合,inflate一个新的View,并且给它setTag,如果convertView不为空,则直接通过getTag获得ViewHolder实例

2.接着调用holder.getView,传入控件ID,如果在该map中还未有过,则通过findViewById找到控件,并存放进该行的View集合中,如果已经存在,则可以进行View的复用,即直接map.get(viewId);

3.最后调用getConvertView,获得我们已经处理好的convertView实例


封装Adapter

上面我们对ViewHolder进行了封装,让adapter的getView方法大大简化,接下来开始封装我们的Adapter
封装Adapter成为公共类,我们需要注意以下问题:
平时我们写Adapter的时候数据类型总是不一样的,比如一会儿是一个User列表,一会儿是一个Car列表,传进来的数据源的类型一般是不一样的,那如何做到不管传进来什么类型都能使用呢?是的没错,又是通过泛型来解决:

public abstract class CommonAdapter<T> extends BaseAdapter{
	
	public Context mContext;
	
	public List<T> list;
	
	public CommonAdapter(Context context, List<T> list){
		this.mContext = context;
		this.list = list;
	}
	
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}
	
	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
		TextView titleTv = holder.getView(R.id.titleTv);
		titleTv.setText(list.get(position));
		return holder.getConvertView();
	}	
	
}



可以看到,我们将Adapter的数据类型代替为泛型的形式,并且我们定义的CommonAdapter为一个抽象类,这样做的原因是在基类先将getCount、getItem等方法给实现了,然后以后的具体Adapter类就只需要继承该CommonAdapter,但是其实开发中我们的getView是每次的操作都是各有所异的,不可能定死,而且仔细看你会发现getView中的生成holder实例的代码和返回convertView实例的代码是千篇一律,区别只在于传进来的item的布局id以及控件的生成不一样罢了,所以可以将这部分不一样的提取出来放在一个抽象方法中,留给子类去实现,将

MainActivity中:CommonAdapter修改如下:

public abstract class CommonAdapter<T> extends BaseAdapter{
	
	public Context mContext;
	
	public List<T> list;
	
	public int layoutId;
	
	public CommonAdapter(Context context, List<T> list, int layoutId){
		this.mContext = context;
		this.list = list;
		this.layoutId = layoutId;
	}
	
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}
	
	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return list.get(position);
	}
	
	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, layoutId, parent);
		convert(holder, list.get(position), position);
		return holder.getConvertView();
	}
	//这个就是留给具体Adapter实现的方法
	public abstract void convert(CommonViewHolder viewHolder, T data, int position);
	
}



至此,我们的通用Adapter打造完毕,接下来我们来看看实践效果:

例子1

先定义一个用于纯文本显示的ListView的Adapter类:

public class TextListViewAdapter extends CommonAdapter<String>{

	public TextListViewAdapter(Context context, List<String> list) {
		super(context, list);
		// TODO Auto-generated constructor stub
	}
	
	public View getView(int position, View convertView, ViewGroup parent) {
		// TODO Auto-generated method stub
		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
		TextView titleTv = holder.getView(R.id.titleTv);
		titleTv.setText(list.get(position));
		return holder.getConvertView();
	}	
	
}



MainActivity中:

public class MainActivity extends Activity {
	
	private ListView listview;
	private CommonAdapter adapter;
	private List<String> data;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		listview = (ListView)this.findViewById(R.id.listview);
		initData();
		adapter = new TextListViewAdapter(this.getApplicationContext(), data);
		listview.setAdapter(adapter);
	}
	
	public void initData(){
		data = new ArrayList<String>();
		for(int i=0; i<20; i++){
			data.add("数据"+i);
		}
	}
}



可以看到,Adapter的代码比以前省去了好多,运行后效果:




例子2

我们再试试多控件的情况,将item_listview布局文件更改如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:layout_marginTop="15dp"
    android:layout_marginBottom="15dp"
    android:orientation="horizontal"
    >
    
    <ImageView 
        android:id="@+id/item_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:src="@drawable/ic_launcher"/>
    
    <LinearLayout 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:orientation="vertical">
        
        <TextView 
	        android:id="@+id/titleTv"
	        android:layout_width="match_parent"
	        android:layout_height="0dp"
	        android:layout_weight="2"
	        android:gravity="bottom"
	        android:paddingTop="10dp"
	        android:paddingBottom="0dp"
	        android:paddingLeft="15dp"
	        android:textSize="15sp"
	        android:textColor="#000"
	        android:text="标题"
	        />
        <TextView 
	        android:id="@+id/detailTv"
	        android:layout_width="wrap_content"
	        android:layout_height="0dp"
	        android:layout_weight="2"
	        android:gravity="top"
	        android:paddingTop="0dp"
	        android:paddingBottom="10dp"
	        android:paddingLeft="15dp"
	        android:textSize="12sp"
	        android:textColor="#676767"
	        android:text="详细内容"
	        />
    </LinearLayout>
    
</LinearLayout>



MainActivity中:

public class MainActivity extends Activity {
	
	private ListView listview;
	
	private CommonAdapter adapter;
	
	private List<ItemBean> data;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		listview = (ListView)this.findViewById(R.id.listview);
		initData();
		adapter = new TextImgAdapter(this.getApplicationContext(), data, R.layout.item_listview);
		listview.setAdapter(adapter);
	}
	
	public void initData(){
		data = new ArrayList<ItemBean>();
		for(int i=0; i<20; i++){
			ItemBean bean = new ItemBean(R.drawable.ic_launcher, "标题"+i, "详细内容"+i);
			data.add(bean);
		}
	}
}

可以看到,做了些更改,数据类型更改为了我们自定义的bean,bean中有三个属性,分别每个ListViewItem中的头像、标题、内容



ItemBean类:

public class ItemBean {
	
	private int imgid;
	private String title;
	private String detail;
	
	public ItemBean() {
		super();
		// TODO Auto-generated constructor stub
	}
	public ItemBean(int imgid, String title, String detail) {
		super();
		this.imgid = imgid;
		this.title = title;
		this.detail = detail;
	}
	public int getImgid() {
		return imgid;
	}
	public void setImgid(int imgid) {
		this.imgid = imgid;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getDetail() {
		return detail;
	}
	public void setDetail(String detail) {
		this.detail = detail;
	}

}



最后再看看我们的Adapter,由于现在的item布局多了一个ImageView以及一个TextView,所以我们的Adapter相应得变成如下:

public class TextImgAdapter extends CommonAdapter<ItemBean>{
	
	public TextImgAdapter(Context context, List<ItemBean> list, int layoutId) {
		super(context, list, layoutId);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void convert(CommonViewHolder viewHolder, ItemBean data, int position) {
		// TODO Auto-generated method stub
		ImageView item_iv = viewHolder.getView(R.id.item_iv);
		TextView titleTv = viewHolder.getView(R.id.titleTv);
		TextView detailTv = viewHolder.getView(R.id.detailTv);
		item_iv.setBackgroundResource(R.drawable.ic_launcher);
		titleTv.setText(data.getTitle());
		detailTv.setText(data.getDetail());
	}

}



只是多了几行控件的生成以及设置值,清晰了很多有木有~~以后有再多的元素,依然只需先生成对应的实例,然后set值,一目了然。

运行结果:



成功实现我们的效果,妈妈再也不用担心我写Adapter写到废寝忘食.........

猜你喜欢

转载自blog.csdn.net/IT_ZJYANG/article/details/52081730