Android如何优雅地实现ListView加载更多功能(1)

来源:互联网 发布:js uint8array 编辑:程序博客网 时间:2024/05/16 06:50

利用ListView的分类型Item功能实现加载更多功能

RecyclerView是Android5.0以后推出的新控件,相比于ListView可定制性更大,大有取代ListView之势。所以你可能会感觉,现在还谈ListView显得有点老套,技术有点out了。但是,不可否认的是,目前,使用ListView用来显示列表的还是占大部分的。所以,作为我日常之余,就抽空写了一个比较通用的ListView的框架。
下面这篇博客主要来实现ListView的上拉加载更多功能。可能也是公司项目开发里面,比较常用的技术实现!这个内容分两次更新,本篇博客将介绍ListView的Adapter的抽取过程。下一次会在本次的基础上,介绍加载更多功能的实现。

主要从以下方面讨论:

  • 对BaseAdapter的四个抽象方法讲解
  • 对getView方法的抽取
  • 加载更多Item的添加
  • 加载更多功能实现

后两部分内容的介绍麻烦查看Android如何优雅地实现ListView加载更多功能(2)

1、对BaseAdapter的四个抽象方法讲解

我们初学安卓时,就已经知道,写一个ListView的Adapter必须实现其以下四个方法,下面,我们先按照初学安卓时的写法先写一个适配器,算是对我们下面内容的一个导入:

public class AnzhiAdapter<T> extends BaseAdapter {    private List<T> mList;    private Context mContext;    public AnzhiAdapter(Context context, List<T> list) {        super();        this.mList = list;        this.mContext = context;    }    @Override    public int getCount() {        return mList.size();    }    @Override    public T getItem(int position) {        return mList.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();            holder.name = new TextView(mContext);            convertView.setTag(holder);        }else{            holder = (ViewHolder) convertView.getTag();        }        //设置数据        //...        return convertView;    }    static class ViewHolder {        TextView name;    }}

可以看到,我们每次写Adapter时,都需要实现这四个方法,然而,写法是如此的相似!重复代码是编程的中的大忌,我们要尽量避免写出重复代码,提高代码质量。再深入的讲,就涉及到代码重构这一高深莫测的学问了,我们不讲。有人说,程序员都很懒,从这就可以看出了,因为我们就连IDE自动生成了大部分的方法都不愿意去写,确实很懒,但是,我认为,这更是我们对精致生活的追求,代表了我们的一种生活态度,那就是不将就!扯远啦。回来再看!

接下来,我们可以对这个类进行一些必要的抽取,以简化我们日后编程的代码量。我们发现以下三个方法的写法,高度一致:

@Override    public int getCount() {        return mList.size();    }    @Override    public T getItem(int position) {        return mList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }

消灭重复代码,我们就从它们开始。所以,我们定义了一个泛型,在构造方法里面,添加了一个list参数用来传递ListView需要的数据集合,将这个list里面的元素的类型限制为T;然后,我们定义了一个成员变量mList来接收数据。所以,以上三个方法就实现完了,而且是一个通用的实现。接下来,我们重点讲解一下getView()这个方法。

2、对getView方法的抽取

我们刚刚开始接触安卓的时候,我们一般采用比较简单的写法,定义一个内部类ViewHolder。然后判断convertView是否为空,为空时,我们才去new一个ViewHolder的对象,不为空时,我们就直接复用convertView对象。这是相当于数学公式一般的写法,我们已经牢牢记住了,但是,这依然是一个高度重复的一个工作,程序员应该“懒”到连这丁点重复代码也不愿意写,还是前面的话,这是我们“不将就”的态度的体现。那么下面我们不妨接着这思路,进一步去想想,getView方法的重复代码,如何进一步进行抽取?

不妨先定义一个类,我们姑且就叫它为BaseHolder,当convertView为空时,我们以前需要去加载Item的布局文件,然后把这个viewHolder对象作为一个Tag设置给convertView进行标记保存。所以可以想到,我们可以将加载布局文件和设置Tag标记的这个过程,放在BaseHolder中完成,并且,只执行一次的过程,我们自然就放在构造器中完成。因为这是一个抽取出来的通用类,将来将给很多不同类型的Adapter提供Holder。所以将来的每一个Item的布局结构是未知的,这里不能去做具体实现。所以我们需要要写一个抽象方法,我们将方法名叫做initView(),有抽象方法了,这个BaseHolder类我们也必须加上abstract,变为一个抽象类。
当convertView不为空时,取出Tag标记强转为ViewHolder对象,这个没有办法进行进一步抽取。接下来就是设置数据的过程,因为Listview中每一个Item对应的数据类型都不一致,所在在父类中,是没法确定每一个条目的数据类型,所以定义一个泛型T,在BaseHolder中定义一个成员变量data,这个对象,将对Item做数据填充用。暴露这个对象的get、set方法作为赋值以及取值使用。当调用set方法为data赋值时,这时,data的数据就需要提供View展示出来,我们将方法名叫做initData(),这时因为前面说了Item的布局是不确定的,设置数据的过程,自然也是不能确定下来的,我们继续将此方法调用成一个抽象方法,让子类去实现。
接下来数据设置完成,按照以前的写法,就该返回convertView这个View了,那么我们在这里,需要返回的就是initView方法里面加载出来的View对象,我们不妨也把它定义成一个成员变量mItemView,暴露一个get方法getItemView(),用于返回这个对象。
以上是分析过程,需要养成习惯的是,需要操作界面的过程,我们都会用到上下文对象,所以,在构造方法中,我们需要添加一个上下文形参,用来接收上下文对象。

下面是BaseHolder的完整代码。

public abstract class BaseHolder<T> {    public Context mContext;    private View mItemView;    private T data;    public BaseHolder(Context context) {        this.mContext = context;        mItemView = initView();        mItemView.setTag(this);    }    public T getData() {        return data;    }    /**     * 设置Item的数据     * 因为Listview中每一个Item对应的数据类型都不一致,所在在父类中,是没法确定每一个条目的数据类型,所以定义一个泛型T     * @param data     */    public void setData(T data) {        this.data = data;        initData();    }    /**     * 返回Item的视图,用于填充ListView     * @return     */    public View getItemView() {        return mItemView;    }    /**     * 初始化每个Item的视图     * 每一个Item的布局结构是未知的,这里不能去做具体实现     * @return     */    public abstract View initView();    /**     * 填充Item的数据     * Item的布局是不确定的,设置数据的过程,自然也是不能确定下来的     */    public abstract void initData();}

上面我们有两个抽象方法,细心的你可能会发现,我把界面加载与数据填充分别抽取成了两个方法,其实这就是基于MVC编程的思想,将视图层与逻辑业务层分离编程。
接下来就是getView方法的书写。根据前面的分析,我们可以很轻松的写出。但是需要注意的是,因为在当前类中,我们还是无法确定Item的布局,所以,我又写了一个抽象方法getHolder(),让子类去帮忙实现。以下是具体代码。

@Override    public View getView(int position, View convertView, ViewGroup parent) {        if (convertView == null) {            itemHolder = getHolder();        } else {            itemHolder = (BaseHolder) convertView.getTag();        }        itemHolder.setData(getItem(position));        return itemHolder.getItemView();    }

下面,是AnzhiBaseAdapter的全部代码。

public abstract class AnzhiBaseAdapter<T> extends BaseAdapter {    private List<T> mList;    private Context mContext;    private BaseHolder itemHolder;    public AnzhiBaseAdapter(Context context, List<T> list) {        super();        this.mList = list;        this.mContext = context;    }    @Override    public int getCount() {        return mList.size();    }    @Override    public T getItem(int position) {        return mList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        if (convertView == null) {            itemHolder = getHolder();        } else {            itemHolder = (BaseHolder) convertView.getTag();        }        itemHolder.setData(getItem(position));        return itemHolder.getItemView();    }    public abstract BaseHolder getHolder() ;}

到现在,我们已经初步完成了对AnzhiBaseAdapter的编写,可以基于上面的代码,写一个例子来测试一下我们的抽取是否出现问题了。大致过程就是新建一个类例如叫MyAdapter继承自AnzhiBaseAdapter,实现getHolder方法,新建一个类MyTestHolder继承自BaseHolder,实现initView,和initData方法,然后在getHolder中返回MyTestHolder的对象。接下来在给ListView设置数据适配器时,设置成MyAdapter的对象即可。是不是非常简单呢?这我就不在这贴出来了,有兴趣的你可以自己写一个demo测试一下。

下一次更博,我将介绍,如何实现ListView的加载更多的功能,今天的内容,是一个基础。

阅读全文
1 0
原创粉丝点击