ListView和GridView简介
在Android App的开发中, ListView和GridView等控件是使用非常频繁的控件。 这两个控件的特点是使用数据适配器来显示数据, 并且在数据项较多的时候, 可以重用用于显示数据的条目(这里的条目指的是ListView或GridView中用于显示每一个数据项的子View)。本文的重点并不是讲解ListView和GridView的内部实现或使用方法,但是后文讲解的自定义Adapter是被ListView和GridView所调用的,所以在这里简单提及ListView和GridView。ListView,GridView和Adapter的关系如下图所示:
适配器的一般使用方式
private static class ScheduleAdapter extends BaseAdapter{
private List<Schedule> meetings ;
private Context context;
private LayoutInflater inflator;
public ScheduleAdapter(Context context ) {
this.context = context;
inflator = LayoutInflater.from(context);
}
public ScheduleAdapter(Context context, List<Schedule> meetings) {
super();
this.context = context;
this.meetings = meetings;
inflator = LayoutInflater.from(context);
}
public List<Schedule> getMeetings() {
return meetings;
}
public void setMeetings(List<Schedule> meetings) {
this.meetings = meetings;
}
@Override
public int getCount() {
return meetings.size();
}
@Override
public Object getItem(int position) {
return meetings.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = null;
if(convertView == null){
v = inflator.inflate(R.layout.schedule_item, null);
ViewHolder holder = new ViewHolder();
holder.name = (TextView) v.findViewById(R.id.schedule_name_id);
holder.time = (TextView) v.findViewById(R.id.schedule_time_id);
holder.icon = (ImageView) v.findViewById(R.id.schedule_icon_id);
v.setTag(holder);
}else{
v = convertView;
}
ViewHolder holder = (ViewHolder) v.getTag();
Schedule data = meetings.get(position);
holder.name.setText(data.getName());
holder.time.setText(data.getStartTime() + " " + data.getEndTime());
return v;
}
private static class ViewHolder{
public TextView name;
public TextView time;
public ImageView icon;
}
}
从上述代码可以看出, getView方法中处理的逻辑有两个:
1 判断是否可以重用View, 如果不能重用, 就创建新的条目View, 找到条目View中的各个子View, 被存放在一个ViewHolder独享中。如果可以重用View, 就重用这个view, 并且从和这个View相关的ViewHolder对象中找到已经缓存的子View。
2 将具体的数据和事件绑定到各个子View上。
实现自动重用View的Adapter
import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/**
* 一个具有自动重用View功能的适配器实现
*
* @author zhangjg
* @date Feb 20, 2014 10:57:42 AM
*/
public abstract class AutoReuseViewAdapter extends BaseAdapter {
//Item布局资源
private int itemLayoutRes;
//Item中要操作的各个子View的Id
private int[] childViewIds;
//布局填充器
private LayoutInflater inflator;
public AutoReuseViewAdapter(Context context, int itemLayoutRes, int ... childViewIds) {
inflator = LayoutInflater.from(context);
this.itemLayoutRes = itemLayoutRes;
this.childViewIds = childViewIds;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = null;
ViewHolder holder;
if(convertView != null){
v = convertView;
holder = (ViewHolder) v.getTag();
}else{
v = inflator.inflate(itemLayoutRes, null);
holder = new ViewHolder();
for(int childId : childViewIds ){
holder.cacheView(v.findViewById(childId));
}
v.setTag(holder);
}
boundDataAndEventToViews(position, v, holder.getCachedViews());
return v;
}
/**
* 抽象方法, 被子类覆盖, 将业务相关的数据和事件绑定到特定的子View上
*/
protected abstract void boundDataAndEventToViews(int position,
View itemView, ArrayList<View> childViews);
/**
* 内部类, 用于缓存条目View的子控件, 同样是面向抽象和业务无关的
*
* @author zhangjg
* @date Feb 20, 2014 9:12:33 AM
*/
private static class ViewHolder{
private ArrayList<View> cachedViews = new ArrayList<View>();
public void cacheView(View v){
cachedViews.add(v);
}
public ArrayList<View> getCachedViews(){
return cachedViews;
}
}
}
首先解释一下这个抽象类的成员变量。 itemLayoutRes是条目View的布局资源, childViewIds是一个int型的数组, 用于存放条目View中要处理的各个子View的id 。 这两个成员变量是通过构造方法注入的, 代码如下:
private int itemLayoutRes;
//Item中要操作的各个子View的Id
private int[] childViewIds;
//布局填充器
private LayoutInflater inflator;
public AutoReuseViewAdapter(Context context, int itemLayoutRes, int ... childViewIds) {
inflator = LayoutInflater.from(context);
this.itemLayoutRes = itemLayoutRes;
this.childViewIds = childViewIds;
}
在构造方法中, 还要根据context创建一个LayoutInflater对象, 用于从XML布局中加载条目View。
1 判断是否可以重用View, 如果不能重用, 就创建新的条目View, 找到条目View中的各个子View, 被存放在一个ViewHolder独享中。如果可以重用View, 就重用这个view, 并且从和这个View相关的ViewHolder对象中找到已经缓存的子View。
2 将具体的数据和事件绑定到各个子View上。
使用自动重用View功能的Adaper
private static class ScheduleAdapter extends /*BaseAdapter*/AutoReuseViewAdapter{
private List<Schedule> meetings ;
public ScheduleAdapter(Context context ) {
super(context, R.layout.schedule_item,
R.id.schedule_name_id, R.id.schedule_time_id, R.id.schedule_icon_id);
}
public ScheduleAdapter(Context context, List<Schedule> meetings) {
this(context);
this.meetings = meetings;
}
public List<Schedule> getMeetings() {
return meetings;
}
public void setMeetings(List<Schedule> meetings) {
this.meetings = meetings;
}
@Override
public int getCount() {
return meetings.size();
}
@Override
public Object getItem(int position) {
return meetings.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
protected void boundDataAndEventToViews(int position, View itemView,
ArrayList<View> childViews) {
Schedule data = meetings.get(position);
((TextView)childViews.get(0)).setText(data.getName());
((TextView)childViews.get(1)).setText(data.getStartTime() + " " + data.getEndTime());
//((ImageView)childViews.get(2)).setImageResource(resId);
}
}
看, 是不是变得简单多了。 我们现在只需要关心业务相关的逻辑了, 不用再关心业务无关的逻辑(重用View的逻辑)。所以, 如果在项目中有一些业务无关的代码总是重复的写, 那么有很大可能有优化的余地, 这时我们就要发扬程序员的”懒惰“的优良传统,将重复的逻辑抽取出来。