Android 通用ListView、GridView适配器

1、简述

在Android开发肯定避免不了与adapter打交道,一般都是继承于BaseAdapter重写里面几个方法,然后一个ListView对应一个Adapter,那自然项目中就出现一大堆Adapter,鉴于此Adapter出现大量的重复代码是否有办法可以简化呢?答案是肯定的。

2、常规写法

只贴adapter部分,layout就不贴了,因为不是重点。

public class SimpleAdapter extends BaseAdapter {
    public static class ViewHolder {
        public TextView tvName;
    }

    private List<Student> studentList;
    private LayoutInflater inflater;

    public SimpleAdapter(Context context, List<Student> students) {
        inflater = LayoutInflater.from(context);
        this.studentList = students;
    }

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

    @Override
    public Student getItem(int position) {
        return studentList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        View view = convertView;
        if (view == null) {
            view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
            holder = new ViewHolder();
            holder.tvName = (TextView) view.findViewById(android.R.id.text1);
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }
        Student student = getItem(position);
        holder.tvName.setText(student.naem);
        return view;
    }
}

Activity中调用

setListAdapter(new SimpleAdapter(getContext(),datas));

这段代码应该不陌生,当继承BaseAdapter,必须重写getCount、getItem、getItemId、getView这4个方法,然后写一个构造方法传入实体对象。可以说你再写一个BaseAdapter也是同样的写法,仔细的你应该会发现,只有构造方法实体对象与getView中方法体业务逻辑有差异嘛,那么是不是可以把实体对象改写成泛型,getView方法体业务逻辑抽象出来,在外实现呢?那是必须可以的,接下来我们一步一步改善这要命的重复代码。

3、实体对象改成泛型

新建一个adapter命名为CygAdapter,有变化的代码加了注释。

public class CygAdapter<T> extends BaseAdapter {//变化
    public static class ViewHolder {
        public TextView tvName;
    }

    private List<T> studentList;//变化
    private LayoutInflater inflater;

    public CygAdapter(Context context, List<T> students) {
        inflater = LayoutInflater.from(context);
        this.studentList = students;
    }

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

    @Override
    public T getItem(int position) {//变化
        return studentList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        View view = convertView;
        if (view == null) {
            view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
            holder = new ViewHolder();
            holder.tvName = (TextView) view.findViewById(android.R.id.text1);
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }
        T student = getItem(position);//变化
        holder.tvName.setText(student.naem);//这里出问题咯,这个T泛型类根本就没有name变量嘛,怎么办?
        return view;
    }
}

上面代码已成功把实体对象改成了泛型,但是getView中的业务逻辑这时出现了上面代码中的问题,无法取到name变量,难道给Student实体类加注解?对于一个Student类注解是行得通滴,加一个@name注解嘛,但是又来一个学校School类、班级Class类那岂不是加N个不同命名的注解?这样一来加注解方案肯定就走不下去了。那么我们可以尝试把getView的业务逻辑抽象出来在外部实现,那现在就需要一个抽象方法了。

4、抽象getView的业务逻辑

我们再CygAdapter改造一下,新建一个抽象方法 onBindData(ViewHolder viewHolder, final T item, final int position);

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        View view = convertView;
        if (view == null) {
            view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
            holder = new ViewHolder();
            holder.tvName = (TextView) view.findViewById(android.R.id.text1);
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }
        T student = getItem(position);
        //holder.tvName.setText(student.naem);//这里出问题咯,怎么办?
        onBindData(holder,student,position);//把上面holder.tvName.setText(student.naem)抽象到外部实现
        return view;
    }
    //新建抽象方法
    public abstract void onBindData(ViewHolder viewHolder, final T item, final int position);

再来看在Activity中用法

        setListAdapter(new com.mylhyl.cygadapter.sample.blog.CygAdapter<Student>(getContext(),datas) {
            @Override
            public void onBindData(ViewHolder viewHolder, Student item, int position) {
                viewHolder.tvName.setText(item.naem);
            }
        });

换一个School对象试试

        ArrayList<School> datas = new ArrayList();
        datas.add(new School("珠海北师大"));
        datas.add(new School("长沙湖南大学"));
        setListAdapter(new com.mylhyl.cygadapter.sample.blog.CygAdapter<School>(getContext(), datas) {
            @Override
            public void onBindData(ViewHolder viewHolder, School item, int position) {
                viewHolder.tvName.setText(item.address);
            }
        });

是不是爽YY了,不管换什么实体对象照样搞定你。Student与School二个实体类都分别只有一个属性name、address,那么现在需求上变化,在Student类中新增一个age年龄属性,显示在ListView上。这样一来我们的layout就得新加一个TextView来用显示age,我们就不新建了利用系统自带的android.R.layout.simple_list_item_2,于是就可以开干了,重新构造List<Student>数据集啥的

        ArrayList<Student> datas = new ArrayList();
        datas.add(new Student(21,"张三"));
        datas.add(new Student(22,"李四"));
        setListAdapter(new com.mylhyl.cygadapter.sample.blog.CygAdapter<Student>(getContext(), datas) {
            @Override
            public void onBindData(ViewHolder viewHolder, Student item, int position) {
                viewHolder.tvName.setText(item.naem);
                viewHolder.tvAge.setText(String.valueOf(item.age));//尼玛没有ViewHolder中没有tvAge呀
            }
        });

上面代码ViewHolder中没有tvAge呀,新建一个不就得了,这样肯定是不行的,与刚才说的加注解方案一样的道理,ViewHolder中的变量只会越来越多,怎么办呢?竟然问题出现在ViewHolder就得从他来下手,我们看getView中的那段ViewHolder相关的代码

没有加age之前

        ViewHolder holder;
        View view = convertView;
        if (view == null) {
            view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);//一个TextView
            holder = new ViewHolder();
            holder.tvName = (TextView) view.findViewById(android.R.id.text1);//名字
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }

加age之后

        ViewHolder holder;
        View view = convertView;
        if (view == null) {
            view = inflater.inflate(android.R.layout.simple_list_item_2, parent, false);//二个TextView
            holder = new ViewHolder();
            holder.tvName = (TextView) view.findViewById(android.R.id.text1);//名字
            holder.tvAge = (TextView) view.findViewById(android.R.id.text2);//年龄
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }

上面代码是不是看出点什么呢?是不是除了inflate与findViewById部分不一样,那是不是有点思路了?新建一个类抽出相同部分,写成一个通用的ViewHolder,开干吧!

5、通用ViewHolder

这个是参考了鸿洋的

新建一个不可继承的CygViewHolder,首先看getView中代码,这个CygViewHolder类肯定不是每都new,只有当convertView为null才new的,所以先搞个静态方法用于取得CygViewHolder,为了不被外部直接new CygViewHolder,我们得把构造方法变为私用。来啊来呀,看改造后的代码啊!

CygViewHolder

public final class CygViewHolder {
    private Context mContext;
    private View mItemView;

    public static CygViewHolder get(Context context, int resource, View convertView, ViewGroup parent) {
        if (convertView == null) {
            View view = LayoutInflater.from(context).inflate(resource, parent, false);
            return new CygViewHolder(context, view);
        }
        return (CygViewHolder) convertView.getTag();
    }

    private CygViewHolder(Context context, View itemView) {
        mContext = context;
        mItemView = itemView;
        mItemView.setTag(this);
    }

    public View getItemView() {
        return mItemView;
    }
}

CygAdapter

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //没有了一堆重复代码
        CygViewHolder viewHolder = CygViewHolder.get(context, resource, convertView, parent);
        T student = getItem(position);
        onBindData(viewHolder, student, position);
        return viewHolder.getItemView();
    }

    //ViewHolder改为CygViewHolder
    public abstract void onBindData(CygViewHolder viewHolder, final T item, final int position);

ListView绑定CygAdapter的时候出现问题了,无法查找控件

        ArrayList<Student> datas = new ArrayList();
        datas.add(new Student(21,"张三"));
        datas.add(new Student(22,"李四"));
        setListAdapter(new com.mylhyl.cygadapter.sample.blog.CygAdapter<Student>(getContext(), datas) {
            @Override
            public void onBindData(CygViewHolder viewHolder, Student item, int position) {
                //查找TextView,还是没有tvName\tvAge?又得如何处理,想个办法
                viewHolder.tvName = (TextView) view.findViewById(android.R.id.text1);
                viewHolder.tvAge = (TextView) view.findViewById(android.R.id.text2);
                //为TextView设置数据
                viewHolder.tvName.setText(item.naem);
                viewHolder.tvAge.setText(String.valueOf(item.age));
            }
        });

上面代码onBindData绑定数据代码段的问题如何处理呢?思路是这样,既然CygViewHolder中已经有了每个Item的View,那么我们可以在CygViewHolder写个findViewById方法查找控件,并缓存在SparseArray<View>中,如果有直接从缓存中返回,否则通过itemView.findViewById查找,缓存再返回。

public final class CygViewHolder {
    private Context mContext;
    private SparseArray<View> mViews;//变化
    private View mItemView;

    public static CygViewHolder get(Context context, int resource, View convertView,
                                    ViewGroup parent) {
        if (convertView == null) {
            View view = LayoutInflater.from(context).inflate(resource, parent, false);
            return new CygViewHolder(context, view);
        }
        return (CygViewHolder) convertView.getTag();
    }

    private CygViewHolder(Context context, View itemView) {
        mContext = context;
        mViews = new SparseArray<>();//变化
        mItemView = itemView;
        mItemView.setTag(this);
    }

    public <T extends View> T findViewById(int id) {//新增方法
        View view = mViews.get(id);
        if (view == null) {
            view = mItemView.findViewById(id);
            mViews.put(id, view);
        }
        return (T) view;
    }
}

使用

        ArrayList<Student> datas = new ArrayList();
        datas.add(new Student(21, "张三"));
        datas.add(new Student(22, "李四"));
        setListAdapter(new com.mylhyl.cygadapter.sample.blog.CygAdapter<Student>
                (getContext(), android.R.layout.simple_list_item_2, datas) {
            @Override
            public void onBindData(CygViewHolder viewHolder, Student item, int position) {
                TextView tvName = viewHolder.findViewById(android.R.id.text1);
                TextView tvAge = viewHolder.findViewById(android.R.id.text2);
                tvName.setText(item.naem);
                tvAge.setText(String.valueOf(item.age));
            }
        });

现在是不是简单多了,adapter通用,只需使用时在onBindData中查到控件、给控件绑定数据,就这么简单。

内容比较多写的啰嗦了点,但出发点是好的,请耐心阅读,勿喷哦!!!

本库地址:https://github.com/mylhyl/Android-CygAdapter

说明一下CygViewHolder中我只加入了TextView.setText的方法,如有需要增加其它方法可以加Q群:435173211 

也可到GitHub先fork后再new pull request 可者Issues

时间: 2024-12-16 21:30:19

Android 通用ListView、GridView适配器的相关文章

Android 快速开发系列 打造万能的ListView GridView 适配器

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38902805 ,本文出自[张鸿洋的博客] 1.概述 相信做Android开发的写得最多的就是ListView,GridView的适配器吧,记得以前开发一同事开发项目,一个项目下来基本就一直在写ListView的Adapter都快吐了~~~对于Adapter一般都继承BaseAdapter复写几个方法,getView里面使用ViewHolder模式,其实大部分的代码基本都是类似的

Android:ListView和适配器的使用

ListView作用:将数据填充到布局 实例:下载>> Listview与适配器ArrayAdapter配合使用,ArrayAdapter比较简单,但它只能用于显示文字.布局文件可以自己写,也可以用系统的. 在布局文件中加入一个ListView控件: <ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wra

[Android Pro] ListView,GridView之LayoutAnimation特殊动画的实现

转载自:http://gundumw100.iteye.com/blog/1874545 LayoutAnimation干嘛用的?不知道的话网上搜一下. Android的Animation之LayoutAnimation使用方法 有两种用法,我的通常写在代码中,像下面这样: /** * Layout动画 * * @return */ protected LayoutAnimationController getAnimationController() { int duration=300; A

Android 高速开发系列 打造万能的ListView GridView 适配器

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38902805 ,本文出自[张鸿洋的博客] 1.概述 相信做Android开发的写得最多的就是ListView,GridView的适配器吧,记得曾经开发一同事开发项目.一个项目下来基本就一直在写ListView的Adapter都快吐了~~~对于Adapter一般都继承BaseAdapter复写几个方法,getView里面使用ViewHolder模式.事实上大部分的代码基本都是相似

Android之ListView/GridView 优化

一.效率最低的getView实现 我们知道,ListView和GridView的显示都是通过Adapter的getView实现的. ListView/GridView数据量较小时,我们的处理方式一般是这样的(效率最低的一种方式) 1 public View getView(int position, View convertView, ViewGroup parent) { 2 View item = mInflater.inflate(R.layout.list_item_icon_text,

Android实现ListView或GridView首行/尾行距离屏幕边缘距离

Android上ListView&GridView默认行都是置顶的,这样会很丑. 一般为了解决这个问题都会在首行或尾行加上一个隐藏的View,那样实在是太麻烦了.在网上看博客的时候突然看到这个属性真的很有用! 直接上关键属性: 设置ListView或GridView的android:clipToPadding = true, 然后通过paddingTop和paddingBottom设置距离就好了. 博客原文: http://www.cnblogs.com/xitang/p/3606578.htm

Android高手进阶教程(十六)之---Android中万能的BaseAdapter(Spinner,ListView,GridView)的使用!

大家好!今天给大家讲解一下BaseAdapter(基础适配器)的用法,适配器的作用主要是用来给诸如(Spinner,ListView,GridView)来填充数据的.而(Spinner,ListView,GridView)都有自己的适配器(记起来麻烦).但是BaseAdapter(一招鲜)对他们来说却是通用的,为什么这么说呢,首先我们看一下API文档: 我们看一下BaseAdapter已经实现了ListAdapter和SpinnerAdapter的接口,而GridView的适配器是实现了List

Android PullToRefresh (ListView GridView 下拉刷新) 使用详解

Android PullToRefresh (ListView GridView 下拉刷新) 使用详解 标签: Android下拉刷新pullToRefreshListViewGridView 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38238749,本文出自:[张鸿洋的博客] 群里一哥们今天聊天偶然提到这个git hub上的控件:pull-to-r

Android listview的适配器以及各种监听、效率的提升

之前写过一篇关于listview的博客,现在感觉那篇博客关于listview认识不够全面.但有些方法还是可取,例如灵活的监听,写适配器.链接在这里android listview长按,单击各种事件捕捉.那个单击监听是一项项设置监听,效率也不好. 不断工作的过程,也是不断总结过程.现在对于listview有了更透彻的理解,所以重新写了一个实践demo.这个demo有涉及到listview的数据源,布局,适配器以及各种监听.效率的提升.现在对于这些理解,想写出一个通用的适配器,但发现还是有点困难,就