通用adapter

一、概述

写项目也有一段时间了,每次写ListView或者是GridView时,只要item内容不同,就要写不同的自定义adapter,感觉很麻烦,其实这些代码都可以抽出来,我们需要做的只是改写getView方法和ViewHolder,那下面我们就开始吧。

有兴趣的可以去这里看看:https://github.com/JoanZapata/base-adapter-helper

二、逻辑实现

首先我们先写一个简单的联系人布局如下图:

联系人

1. 从标准自定义adapter写法开始

下面就是我认为比较标准的自定义adapter写法,我们从这一步一步开始分析哪些可以抽出来,避免重写代码。

/**
 * 适配器的标准写法
 * 
 * @author LiYang
 * 
 */
public class ContantAdapter extends BaseAdapter {
    private Context context;
    private List<ContantBean> list;

    public ContantAdapter(Context context, List<ContantBean> list) {
        this.context = context;
        this.list = list;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public ContantBean 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) {
        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(
                    R.layout.item_main, null);
            holder.ivIcon = (ImageView) convertView
                    .findViewById(R.id.iv_item_icon);
            holder.tvNumber = (TextView) convertView
                    .findViewById(R.id.tv_item_number);
            holder.cb = (CheckBox) convertView.findViewById(R.id.cb_item_check);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        ContantBean bean = list.get(position);
        holder.ivIcon.setImageResource(bean.getImgRes());
        holder.tvNumber.setText(bean.getNumber());
        holder.cb.setChecked(bean.isChecked());

        return convertView;
    }

    class ViewHolder {
        ImageView ivIcon;
        TextView tvNumber;
        CheckBox cb;
    }
}

MainActivity代码:

    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ListView lv = (ListView) findViewById(R.id.lv);
            List<ContantBean> list = new ArrayList<ContantBean>();
            ContantBean bean;
            for (int i = 0; i < 5; i++) {
                // 循环获取资源文件中类似的资源id,如图片icon1,icon2...
                int imgRes = getResources().getIdentifier("icon" + (i + 1),
                        "drawable", getPackageName());
                bean = new ContantBean(imgRes, "131-1565-156" + i, true);
                list.add(bean);
            }
            lv.setAdapter(new ContantAdapter(this, list));
        }
    }

可以发现,继承BaseAdapter中重写的getCount()、getItem(int position)、getItemId(int position)这三个方法的写法是相对固定的,我们可以把它抽出来。

我们现在就可以开始写通用的adapter了,新建类MyBaseAdapter,我们上面分析了,重写的四个方法中只有getView()是我们每次都需要重写的,因为这个方法负责了每个item的展示和数据的加载显示,我们可以先把getView这个代码块的实现放到外部,如下:

2. getView()方法的抽出——加载数据到视图

我们会发现,getView()中代码挺多的,具体该怎么抽出来呢,我们可以先把 视图加载数据这块代码抽出来,写成一个内部接口,并在构造方法中以参数传入,这样在初始化adapter就会实现这个接口,通过接口回调实现getView()代码块:

public class MyBaseAdapter extends BaseAdapter {
    ...
    private LoadData loadData;

    public MyBaseAdapter(Context context, List<ContantBean> list,
            LoadData loadData) {
        ...
        this.loadData = loadData;
    }

    public interface LoadData {
        /**
         * 加载数据到视图
         */
        void convert(ViewHolder holder, ContantBean bean);
    }
    ...
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(
                    R.layout.item_main, null);
            holder.ivIcon = (ImageView) convertView
                    .findViewById(R.id.iv_item_icon);
            holder.tvNumber = (TextView) convertView
                    .findViewById(R.id.tv_item_number);
            holder.cb = (CheckBox) convertView.findViewById(R.id.cb_item_check);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        ContantBean bean = list.get(position);
        loadData.convert(holder, bean);

        // holder.ivIcon.setImageResource(bean.getImgRes());
        // holder.tvNumber.setText(bean.getNumber());
        // holder.cb.setChecked(bean.isChecked());

        return convertView;
    }

    public class ViewHolder {...}

改变的就是加入一个接口,构造方法中传入,在getView()方法中回调接口方法convert,从而把数据加载到视图抽出来了,相应的MainActivity中也要变化:

    MyBaseAdapter adapter = new MyBaseAdapter(this, list, new LoadData() {
        @Override
        public void convert(ViewHolder holder, ContantBean bean) {
            holder.ivIcon.setImageResource(bean.getImgRes());
            holder.tvNumber.setText(bean.getNumber());
            holder.cb.setChecked(bean.isChecked());
        }
    });
    lv.setAdapter(adapter);

    // lv.setAdapter(new ContantAdapter(this, list));

我们发现上面的显然很不合理,ViewHolder负责的是整个item里面的所有需要改变的控件的初始化以及为了优化和convertView绑定这两部分逻辑,我们可以把这个逻辑都放在ViewHolder里面实现;

3. getView方法继续的抽出-通用ViewHolder

具体实现思路:

  1. ViewHolder包含一个item中的所有需要使用的控件,所以我们需要有一个集合(SparseArray)来保存这个控件,从而把每个控件初始化(即findViewById)的过程放到ViewHolder中实现;
  2. 在ViewHolder中实现ListView优化;
  3. 其他一些细节的实现。

代码如下,注解很详细:

/**
 * 通用ViewHolder
 * 
 * @author LiYang
 * 
 */
public class MyViewHolder {
    /**
     * 键值对存放view视图,键-int,值-View
     */
    private SparseArray<View> views;

    /**
     * item布局视图
     */
    private View convertView;

    /**
     * 私有构造方法,进行初始化操作,类的初始化放在静态方法getViewHolder中
     */
    private MyViewHolder(ViewGroup parent, int resId, Context context) {
        views = new SparseArray<View>();
        convertView = LayoutInflater.from(context)
                .inflate(resId, parent, false);
        getConvertView().setTag(this);
    }

    /**
     * 静态方法,获取一个MyViewHolder实例,从getView()方法中的逻辑思路来写,先判断convertView是否为空,
     * 为空就先初始化convertView
     * 、初始化ViewHolder、绑定ViewHolder到view上,主要就是获取到ViewHolder实例来把数据加载到视图上
     * 
     */
    public static MyViewHolder getViewHolder(View convertView,
            ViewGroup parent, int resId, Context context) {
        if (convertView == null) {
            return new MyViewHolder(parent, resId, context);
        }
        return (MyViewHolder) convertView.getTag();
    }

    /**
     * 通过资源ID获取控件
     * 
     * @param resId
     * @return
     */
    public View getView(int resId) {
        View v = views.get(resId);
        if (v == null) {
            v = getConvertView().findViewById(resId);
            views.put(resId, v);
        }
        return v;
    }

    public View getConvertView() {
        return convertView;
    }
}

当然,改变了ViewHolder的写法,相应的Adapter也得修改,新增的就是布局文件要传入,还有就是把getView()中方法体都提取出来, 变为下列形式:

public class MyBaseAdapter extends BaseAdapter {
    ...
    private int resId;

    public MyBaseAdapter(Context context, List<ContantBean> list,
            LoadData loadData, int resId) {
        ...
        this.resId = resId;
    }

    public interface LoadData {
        void convert(MyViewHolder holder, ContantBean bean);
    }

    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        MyViewHolder holder = MyViewHolder.getViewHolder(convertView, parent,
                resId, context);
        loadData.convert(holder, list.get(position));
        return holder.getConvertView();
    }

    //  public class ViewHolder {
    //      public ImageView ivIcon;
    //      public TextView tvNumber;
    //      public CheckBox cb;
    //  }
}

4.最后的改造

查看上面我们写的代码,发现几处需要改进的地方:

  1. 使用接口的方式比较麻烦,可以改为抽象类,这样在初始化时就必须实现抽象方法convert;
  2. 通用ViewHolder可以实现更多的一些方法,比如设置字体,设置背景等等;
  3. 可以试着支持更多的数据类型,而不是自己写的ContantBean;
  4. 代码可以更加简洁。

最终代码如下:
MyBaseAdapter:

/**
 * 通用Adapter
 * 
 * @author LiYang
 * @param <T>
 *            要填充的数据bean
 * 
 */
public abstract class MyBaseAdapter<T> extends BaseAdapter {
    private Context context;
    private List<T> list;
    private int resId;

    public MyBaseAdapter(Context context, List<T> list, int resId) {
        this.context = context;
        this.list = list;
        this.resId = resId;
    }

    /**
     * 加载数据到视图,在初始化时必须实现该方法
     * 
     * @param holder
     *            视图项
     * @param bean
     *            数据项
     */
    public abstract void convert(MyViewHolder holder, T bean);

    @Override
    public int getCount() {
        return 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) {
        MyViewHolder holder = MyViewHolder.getViewHolder(convertView, parent,
                resId, context);
        convert(holder, list.get(position));
        return holder.getConvertView();
    }
}

MyViewHolder:

/**
 * 通用ViewHolder
 * 
 * @author LiYang
 * 
 */
public class MyViewHolder {
    /**
     * 键值对存放view视图,键-int,值-View
     */
    private SparseArray<View> views;

    /**
     * item布局视图
     */
    private View convertView;

    /**
     * 私有构造方法,进行初始化操作,类的初始化放在静态方法getViewHolder中
     */
    private MyViewHolder(ViewGroup parent, int resId, Context context) {
        views = new SparseArray<View>();
        convertView = LayoutInflater.from(context)
                .inflate(resId, parent, false);
        getConvertView().setTag(this);
    }

    /**
     * 静态方法,获取一个MyViewHolder实例,从getView()方法中的逻辑思路来写,先判断convertView是否为空,
     * 为空就先初始化convertView
     * 、初始化ViewHolder、绑定ViewHolder到view上,主要就是获取到ViewHolder实例来把数据加载到视图上
     * 
     */
    public static MyViewHolder getViewHolder(View convertView,
            ViewGroup parent, int resId, Context context) {
        if (convertView == null) {
            return new MyViewHolder(parent, resId, context);
        }
        return (MyViewHolder) convertView.getTag();
    }

    /**
     * 通过资源ID获取控件
     * 
     * @param viewId
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends View> T getView(int viewId) {
        View v = views.get(viewId);
        if (v == null) {
            v = getConvertView().findViewById(viewId);
            views.put(viewId, v);
        }
        return (T) v;
    }

    public View getConvertView() {
        return convertView;
    }

    /*-----------以下是我们可以扩充的方法-------------*/

    public void setBackgroundResource(int viewId, int resId) {
        getView(viewId).setBackgroundResource(resId);
    }

    public void setText(int viewId, String text) {
        TextView v = getView(viewId);
        v.setText(text);
    }

    public void setImageResource(int viewId, int resId) {
        ImageView iv = getView(viewId);
        iv.setImageResource(resId);
    }
}

三、简单的使用

使用就很简单了,直接通过初始化,把布局和list集合穿进去,实现convert方法即可,如下:

MyBaseAdapter<ContantBean> adapter = new MyBaseAdapter<ContantBean>(
        this, list, R.layout.item_main) {

    @Override
    public void convert(MyViewHolder holder, ContantBean bean) {
        holder.setText(R.id.tv_item_number, bean.getNumber());
        holder.setImageResource(R.id.iv_item_icon, bean.getImgRes());
        ((CheckBox) holder.getView(R.id.cb_item_check))
                .setChecked(false);
    }
};
lv.setAdapter(adapter);

源代码在这:下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值