Android 一起来封装一个简单易用的Adapter

前言

还记得我初学 Android 没多久又需要用到 ListView 的时候还不会写 Adapter,结果我居然硬生生的用 TableLayout 和 LinearLayout 把 ListView 给替代了。现在回过神了想想,我当时还真是厉害啊,在另一种意义上。。

其实要会写 Adapter,乃至于封装一个能提高生产效率的 Adapter,是离不开对 ListView、GridView 到 RecyclerView 等一系列视图的Item复用机制的理解和掌握的。不熟悉的朋友们请移驾
http://blog.csdn.net/lmj623565791/article/details/24333277

正文

我还依稀记得最早是这么写一个 Adapter 的(为什么是依稀呢?)

BaseAdapter adapter = new BaseAdapter() {
        @Override
        public int getCount() {
            return list.size();
        }

        @Override
        public String 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) {
            convertView = inflater.inflate(layoutId, parent, false);
            
            TextView textView = convertView.findViewById(textViewId);
            Button button = convertView.findViewById(buttonId);
            
            String data = getItem(position);
            textView.setText(data);
            button.setOnClickListener(onClickListener);
            
            return convertView;
        }
    };

后来我才意识到,这样子写在滑动的时候会很频繁的去 inflate,开销很大,是不合格的写法,于是改成了下面这样:

BaseAdapter adapter = new BaseAdapter() {
        @Override
        public int getCount() {
            return list.size();
        }

        @Override
        public String 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 viewHolder;
            if (convertView == null) {
                convertView = inflater.inflate(layoutId, parent, false);

                viewHolder = new ViewHolder();
                viewHolder.textView = convertView.findViewById(textViewId);
                viewHolder.button = convertView.findViewById(buttonId);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            String data = getItem(position);
            viewHolder.textView.setText(data);
            viewHolder.button.setOnClickListener(onButtonClickeListener);

            convertView.setTag(viewHolder);
            convertView.setOnClickListener(onConvertViewClickedListener);
            return convertView;
        }
    };
class ViewHolder {  
        TextView textView;  
        Button button;  
    } 

这样子写基本上没有遇到遇到太大问题,也就最初容易遇到因为 item 复用导致的各种混乱,在理解了复用机制之后其实很容易避免。我也曾经见到过将 convertView 传入 ViewHolder 中,在 viewHolder 里去实现大部分逻辑的写法,这里就不一一赘述了。

现在来想一下,写一个 Adapter,我们要做些什么?大致有这些吧:
1.实现 BaseAdapter 的 getCount、getItem、getItemId 这三个方法;
2.新建一个 ViewHolder 类,当中要包含一个 item 中需要操作的所有 view;
3.在 BaseAdapter 的 getView 方法中实现 item 复用,处理好 viewHolder 与 convertView 的关系;
4.处理 item,设置数据至 view ,添加监听等。

看上去也不太多嘛,就4步。但是实际上多写几次就很难受了,要是项目中 ListView、GridView 特别多,估计能把一只猿活活写吐了!

因为1、2、3的代码基本上每次都差不多,不同之处大多都在于4。一个复杂的 ListView 写下来感觉腰酸背痛手抽筋,简单的则感觉不会再爱了,毕竟花在写4的时间上好像远远小于123的重复工作。久而久之,我一听到要做 ListView 就会这样 --> T_T

直到在一个月黑风高的夜晚,我意外得到了一件神器

public class ViewHolder {  
    public static <T extends View> T get(View view, int id) {  
        SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();  
        if (viewHolder == null) {  
            viewHolder = new SparseArray<View>();  
            view.setTag(viewHolder);  
        }  
        View childView = viewHolder.get(id);  
        if (childView == null) {  
            childView = view.findViewById(id);  
            viewHolder.put(id, childView);  
        }  
        return (T) childView;  
    }  
} 

从代码上看,这个 ViewHolder 类会往传入的 view 中存一系列 id-view 的键值对作为 tag,代码并不复杂,相信大家都看得懂。
我们再看看这神器到底能干什么

BaseAdapter adapter = new BaseAdapter() {  
        @Override  
        public int getCount() {  
            return list.size();  
        }  
  
        @Override  
        public String 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) {  
            if (convertView == null) {  
                convertView = inflater.inflate(layoutId, parent, false);  
            }  
              
            TextView textView = ViewHolder.get(convertView, textViewId);  
            Button button = ViewHolder.get(convertView, buttonId);  
              
            String data = getItem(position);  
            textView.setText(data);  
            button.setOnClickListener(onButtonClickListener);  
              
            return convertView;  
        }  
    }; 

完全不用专门写 ViewHolder 类了有木有,对于我这样的懒人简直就是福音得意
以上就是神器 ViewHolder 的功效。

难道这是全部了吗?作为一个懒惰的程序猿,相信大家肯定和我一样不满足于此。放眼上述的代码,不管是哪个 adapter 都写了几个重复的方法,那些方法要怎么干掉呢?

其实,一个简单的基类加上对泛型的支持就可以了

public abstract class ListAdapter<T> extends BaseAdapter {  
  
    protected abstract void setItem(View convertView, T data, int position);  
      
    protected List<T> mData;  
    protected Context mContext;  
    protected LayoutInflater mInflater;  
    protected int mLayoutRes;  
  
    public ListAdapter(Context context, List<T> data, int layoutRes) {  
        this.mData = data;  
        this.mContext = context;  
        this.mLayoutRes = layoutRes;  
          
        this.mInflater = LayoutInflater.from(mContext);  
    }  
  
    /** 
     * 刷新 adapter 
     * 考虑到可能会发生数据完全改变的情况,故提供此方法 
     * @param data 
     */  
    public void refresh(List<T> data) {  
        try {  
            this.mData = data;  
            notifyDataSetChanged();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    @Override  
    public int getCount() {  
        return mData.size();  
    }  
  
    @Override  
    public T getItem(int position) {  
        return mData.get(position);  
    }  
  
    @Override  
    public long getItemId(int position) {  
        return position;  
    }  
  
    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        try {  
            if (convertView == null) {  
                convertView = mInflater.inflate(mLayoutRes, null);  
            }  
  
            setItem(convertView, getItem(position), position);  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return convertView;  
    }  
  
    public <V extends View> V getChildView(View view, int id) {  
        return ViewHolder.get(view, id);  
    }  
} 

封装到此结束,让我们看看是怎么使用的吧

BaseAdapter adapter = new ListAdapter<String>(MainActivity.this, list, layoutId) {  
        @Override  
        protected void setItem(View convertView, String data, int position) {  
            TextView textView = getChildView(convertView, textViewId);  
            textView.setText(data);  
              
            getChildView(convertView, buttonId)  
                    .setOnClickListener(onButtonClickListener);  
        }  
    };  

没仔细看前面代码的围观群众:WTF?这就完了?

没错,传入上下文、数据集合,还有布局文件,重写一个 setItem 方法就完了。相比最初的 adapter,我们只需要专注于布局、数据和视图的绑定,已经事件的监听,不需要重写相同的代码,不需要写累赘的 ViewHolder 类,不需要写八股文一般的 Item 复用代码。

虽然并不完美,也还没做到极简,但一个简单好用的 Adapter 已经初步成型了。谢谢各位看官赏脸看到现在。

下一篇文章打算在这次的 ListAdapter 的基础上封装 RecyclerView 的 Adapter,并且提供对 ItemType 的支持方式的参考思路。

项目源码:https://github.com/neverwoodsS/zy-open

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容