当你的才华还撑不起你的梦想时,你只能一直前进!

打造android偷懒神器———ListView的万能适配器

如果你去做任何一个项目,我相信你都会跟我有一样的经历,最最普遍的就是列表显示ListView,当然,写N个自定义的适配器也是情理之中。虽说程序员本身就是搬砖,做这些枯燥无味的重复的事情也是理所当然,但不得不说,谁都想做点高效率的事情的。

而我们一向写的自定义适配器,无非就是继承ArrayAdapter,或者继承自BaseAdapter,然后重写4个方法,前三个方法基本相同,不同在于getView方法,getView里面为了减少绑定和View的重建,又会引入一个静态类ViewHolder,我相信下面这段代码你一点见过不少。

 1 package com.example.nanchen.commonadapterforlistviewdemo;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 import android.widget.ImageView;
 9 import android.widget.TextView;
10 
11 import java.util.List;
12 
13 /**
14  * 常见的ListView的Adapter适配器
15  * Created by 南尘 on 16-7-28.
16  */
17 public class MyListAdapter extends BaseAdapter {
18     private Context context;
19     private List<Data> list;
20 
21     public MyListAdapter(Context context, List<Data> list) {
22         this.context = context;
23         this.list = list;
24     }
25 
26     @Override
27     public int getCount() {
28         return list == null ? 0 : list.size();
29     }
30 
31     @Override
32     public Object getItem(int position) {
33         return list.get(position);
34     }
35 
36     @Override
37     public long getItemId(int position) {
38         return position;
39     }
40 
41     @Override
42     public View getView(int position, View convertView, ViewGroup viewGroup) {
43         MyViewHolder holder = null;
44         if (convertView == null) {
45             convertView = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
46             holder = new MyViewHolder();
47             holder.iv = (ImageView) convertView.findViewById(R.id.item_image);
48             holder.tv = (TextView) convertView.findViewById(R.id.item_text);
49             convertView.setTag(holder);
50         } else {
51             holder = (MyViewHolder) convertView.getTag();
52         }
53         Data data = (Data) getItem(position);
54         holder.iv.setImageResource(data.getImageId());
55         holder.tv.setText(data.getText());
56         return convertView;
57     }
58 
59     public static class MyViewHolder {
60         ImageView iv;
61         TextView tv;
62     }
63 }

可以毫不犹豫地说这个东西我现在闭着眼睛都能流利地写出来,可见少了数百次是难以做到的。

有时候我们也想盛点时间去打点小地主,撩下小妹子,如果要是可以打造一个万能的适配器就好了。

 

仔细观察上面的Adapter,的确是前三个方法一样。我们要是可以全部抽出来就好了。所以可以抽出来,写一个泛型使其变成一个抽象的基类,继承自BaseAdapter.其子类只需要去关心其getView方法。

 1 public abstract class MyListAdapter<T> extends BaseAdapter {
 2     private Context context;
 3     private List<T> list;
 4 
 5     public MyListAdapter(Context context, List<T> list) {
 6         this.context = context;
 7         this.list = list;
 8     }
 9 
10     @Override
11     public int getCount() {
12         return list == null ? 0 : list.size();
13     }
14 
15     @Override
16     public Object getItem(int position) {
17         return list.get(position);
18     }
19 
20     @Override
21     public long getItemId(int position) {
22         return position;
23     }
24 }

 

好像没什么不对,但是这也没解决多少问题呀,要是我们在写大项目的时候还可以抽点时间出来打LOL拿个首胜什么的就更好了。

再来看看getView方法,基本都是先判断ViewHolder是否为空,为空则去Inflate一个xml文件进来,再绑定下视图,设置一个标记,不为空的时候直接引用标记。

或许这里我们可以试一下在ViewHolder上做点什么。

我们要是想把ViewHolder提取出来,只能把每一个Item都固定在ViewHolder里面,而Item又不是固定的,怎么办?

要是我们可以把这个Item直接作为参数传进来就好了,可是传控件好像不能区分,仔细一想,我们能看到一个控件对应着一个id,这个好像可以用HashMap的键值对处理。

而键值由于是Int型的,在新的java API中明确表示在键值为Integer的HashMap中我们要用SparseArray作代替,这样不仅简单,而且性能更优。

我们尝试着封装一下ViewHolder

 1 public class ViewHolder {
 2     //现在对于int作为键的官方推荐用SparseArray替代HashMap
 3     private final SparseArray<View> views;
 4     private int position;
 5     private View convertView;
 6     private Context context;
 7 
 8     private ViewHolder(Context context,ViewGroup parent, int layoutId, int position) {
 9         this.context = context;
10         this.views = new SparseArray<>();
11         this.convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
12         convertView.setTag(this);
13     }
14 
15     /**
16      * 拿到一个ViewHolder对象
17      */
18     public static ViewHolder get(View convertView, ViewGroup parent, int layoutId, int position) {
19         if (convertView == null) {
20             return new ViewHolder(parent.getContext(),parent, layoutId, position);
21         }
22         return (ViewHolder) convertView.getTag();
23     }
24 
25     /**
26      * 通过控件的Id获取对于的控件,如果没有则加入views
27      */
28     public <T extends View> T getView(int viewId) {
29         View view = views.get(viewId);
30         if (view == null) {
31             view = convertView.findViewById(viewId);
32             views.put(viewId, view);
33         }
34         return (T) view;
35     }
36 }

这样的话我们的getView可能会变成这样。

1 @Override  
2     public View getView(int position, View convertView, ViewGroup parent){  
3         ViewHolder viewHolder = ViewHolder.get(convertView, parent,  
4                 R.layout.list_item, position);  
5         TextView mTitle = viewHolder.getView(R.id.id_tv_title);  
6         mTitle.setText(((Data) list.get(position)).getText());  
7         //这里就不设置ImageView了
8         return viewHolder.getConvertView();  
9     } 

 

好吧。与其这样。我们不如直接写在Activity中。

并且如果我们想设置东西也许可以在Holder里面设置,我们可以试一试。

封装后的ViewHolder是这样。

 1 package com.example.nanchen.commonadapterforlistviewdemo;
 2 
 3 import android.content.Context;
 4 import android.graphics.Bitmap;
 5 import android.util.SparseArray;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.ImageView;
10 import android.widget.TextView;
11 
12 import com.squareup.picasso.Picasso;
13 
14 /**
15  * 万能适配器的ViewHolder
16  * Created by 南尘 on 16-7-28.
17  */
18 public class ViewHolder {
19     //现在对于int作为键的官方推荐用SparseArray替代HashMap
20     private final SparseArray<View> views;
21     private int position;
22     private View convertView;
23     private Context context;
24 
25     private ViewHolder(Context context,ViewGroup parent, int layoutId, int position) {
26         this.context = context;
27         this.views = new SparseArray<>();
28         this.convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
29         convertView.setTag(this);
30     }
31 
32     /**
33      * 拿到一个ViewHolder对象
34      */
35     public static ViewHolder get(View convertView, ViewGroup parent, int layoutId, int position) {
36         if (convertView == null) {
37             return new ViewHolder(parent.getContext(),parent, layoutId, position);
38         }
39         return (ViewHolder) convertView.getTag();
40     }
41 
42     /**
43      * 通过控件的Id获取对于的控件,如果没有则加入views
44      */
45     public <T extends View> T getView(int viewId) {
46         View view = views.get(viewId);
47         if (view == null) {
48             view = convertView.findViewById(viewId);
49             views.put(viewId, view);
50         }
51         return (T) view;
52     }
53 
54     public View getConvertView() {
55         return convertView;
56     }
57 
58     /**
59      * 设置字符串
60      */
61     public ViewHolder setText(int viewId,String text){
62         TextView tv = getView(viewId);
63         tv.setText(text);
64         return this;
65     }
66 
67     /**
68      * 设置图片
69      */
70     public ViewHolder setImageResource(int viewId,int drawableId){
71         ImageView iv = getView(viewId);
72         iv.setImageResource(drawableId);
73         return this;
74     }
75 
76     /**
77      * 设置图片
78      */
79     public ViewHolder setImageBitmap(int viewId, Bitmap bitmap){
80         ImageView iv = getView(viewId);
81         iv.setImageBitmap(bitmap);
82         return this;
83     }
84 
85     /**
86      * 设置图片
87      */
88     public ViewHolder setImageByUrl(int viewId,String url){
89         Picasso.with(context).load(url).into((ImageView) getView(viewId));
90 //        ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context));
91 //        ImageLoader.getInstance().displayImage(url, (ImageView) getView(viewId));
92         return this;
93     }
94 
95     public int getPosition(){
96         return position;
97     }
98 }

上面的图片网络加载我用Picasso加载框架,这个网上很多图片加载框架,我前面博客也有很多Demo,大家可以自行查找。

再看看我们的万能适配器,这里我们把它写做一个抽象类,传入一个泛型作为参数。

 1 package com.example.nanchen.commonadapterforlistviewdemo;
 2 
 3 import android.content.Context;
 4 import android.view.View;
 5 import android.view.ViewGroup;
 6 import android.widget.BaseAdapter;
 7 
 8 import java.util.List;
 9 
10 /**
11  * 打造ListView的万能适配器
12  * Created by 南尘 on 16-7-28.
13  */
14 public abstract class CommonAdaper<T> extends BaseAdapter {
15     private Context context;
16     private List<T> list;
17 
18 
19     public CommonAdaper(Context context, List<T> list) {
20         this.context = context;
21         this.list = list;
22     }
23 
24     @Override
25     public int getCount() {
26         return list == null ? 0 : list.size();
27     }
28 
29     @Override
30     public T getItem(int position) {
31         return list.get(position);
32     }
33 
34     @Override
35     public long getItemId(int position) {
36         return position;
37     }
38 
39     @Override
40     public View getView(int i, View view, ViewGroup viewGroup) {
41         ViewHolder holder = ViewHolder.get(view,viewGroup,R.layout.list_item,i);
42         convert(holder,getItem(i));
43         return holder.getConvertView();
44     }
45 
46     public abstract void convert(ViewHolder holder,T item);
47 
48 }

再看看我们主页面怎么调用的。

 1 package com.example.nanchen.commonadapterforlistviewdemo;
 2 
 3 import android.support.v7.app.AppCompatActivity;
 4 import android.os.Bundle;
 5 import android.widget.ListView;
 6 
 7 import java.util.ArrayList;
 8 import java.util.List;
 9 
10 public class MainActivity extends AppCompatActivity {
11 
12     private ListView listView;
13     private List<Data> list;
14 
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19 
20         listView = (ListView) findViewById(R.id.main_lv);
21         initList();
22 
23         listView.setAdapter(new CommonAdaper<Data>(this,list) {
24             @Override
25             public void convert(ViewHolder holder, Data item) {
26                 holder.setText(R.id.item_text,item.getText());
27                 if (item.getImageUrl() != null){
28                     holder.setImageByUrl(R.id.item_image,item.getImageUrl());
29                 }else {
30                     holder.setImageResource(R.id.item_image,item.getImageId());
31                 }
32             }
33         });
34     }
35 
36     private void initList() {
37         list = new ArrayList<>();
38         for (int i = 0; i < 5; i++) {
39             list.add(new Data("本地 "+i,R.mipmap.ic_launcher));
40         }
41 
42         for (int i = 0; i < 5; i++) {
43             list.add(new Data("网络 "+i,"http://pic.cnblogs.com/face/845964/20160301162812.png"));
44         }
45     }
46 }

最后上我写的两个xml

一个是Activity_main.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context="com.example.nanchen.commonadapterforlistviewdemo.MainActivity">
 8 
 9 
10     <ListView
11         android:layout_width="match_parent"
12         android:layout_height="match_parent"
13         android:id="@+id/main_lv"/>
14 </RelativeLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal">

    <ImageView
        android:id="@+id/item_image"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@mipmap/ic_launcher"/>


    <TextView
        android:id="@+id/item_text"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="内容"/>

</LinearLayout>

这个你想怎么定义就想定义了。

本人亲测这个东西可以用,还没入坑的小伙伴赶快入坑吧。

明天我可能还会带来万能的RecyclerView的适配器,还请大家持续关注~~~~

 

 

———————————————————————————————————————————————————————————————————————————— 

7月30日补充:

今天在自己思考写最近大火的RecyclerView的万能适配器的时候参考这边的时候,发现在MainActivity中竟然不能设置其他的布局,才发现自己之前的逻辑有点问题,应该把Layout的ID也作为参数传到Adater去中。看来自己有时候想的也不是很周到,大家看的时候也要多加思考呀。

这是改动后的Adpter和ViewHolder

 1 package com.example.nanchen.commonadapterforlistviewdemo;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 
 9 import java.util.List;
10 
11 /**
12  * 打造ListView的万能适配器
13  * Created by 南尘 on 16-7-28.
14  */
15 public abstract class CommonAdaper<T> extends BaseAdapter {
16     private Context context;
17     private List<T> list;
18     private LayoutInflater inflater;
19     private int itemLayoutId;
20 
21 
22     public CommonAdaper(Context context, List<T> list,int itemLayoutId) {
23         this.context = context;
24         this.list = list;
25         this.itemLayoutId = itemLayoutId;
26         inflater = LayoutInflater.from(context);
27     }
28 
29     @Override
30     public int getCount() {
31         return list == null ? 0 : list.size();
32     }
33 
34     @Override
35     public T getItem(int position) {
36         return list.get(position);
37     }
38 
39     @Override
40     public long getItemId(int position) {
41         return position;
42     }
43 
44     @Override
45     public View getView(int position, View convertView, ViewGroup parent) {
46         ViewHolder holder = getViewHolder(position,convertView,parent);
47         convert(holder,getItem(position));
48         return holder.getConvertView();
49     }
50 
51     public abstract void convert(ViewHolder holder,T item);
52 
53     private ViewHolder getViewHolder(int position,View convertView,ViewGroup parent){
54         return ViewHolder.get(context,convertView,parent,itemLayoutId,position);
55     }
56 
57 }

思想里面应该很清楚了吧。

 1 package com.example.nanchen.commonadapterforlistviewdemo;
 2 
 3 import android.content.Context;
 4 import android.graphics.Bitmap;
 5 import android.util.SparseArray;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.ImageView;
10 import android.widget.TextView;
11 
12 import com.squareup.picasso.Picasso;
13 
14 /**
15  * 万能适配器的ViewHolder
16  * Created by 南尘 on 16-7-28.
17  */
18 public class ViewHolder {
19     //现在对于int作为键的官方推荐用SparseArray替代HashMap
20     private final SparseArray<View> views;
21     private View convertView;
22     private Context context;
23 
24     private ViewHolder(Context context,ViewGroup parent,int itemLayoutId,int position) {
25         this.context = context;
26         this.views = new SparseArray<>();
27         this.convertView = LayoutInflater.from(context).inflate(itemLayoutId,parent,false);
28         convertView.setTag(this);
29     }
30 
31     /**
32      * 拿到一个ViewHolder对象
33      */
34     public static ViewHolder get(Context context,View convertView, ViewGroup parent, int layoutId, int position) {
35         if (convertView == null) {
36             return new ViewHolder(context,parent, layoutId, position);
37         }
38         return (ViewHolder) convertView.getTag();
39     }
40 
41     /**
42      * 通过控件的Id获取对于的控件,如果没有则加入views
43      */
44     public <T extends View> T getView(int viewId) {
45         View view = views.get(viewId);
46         if (view == null) {
47             view = convertView.findViewById(viewId);
48             views.put(viewId, view);
49         }
50         return (T) view;
51     }
52 
53     public View getConvertView() {
54         return convertView;
55     }
56 
57     /**
58      * 设置字符串
59      */
60     public ViewHolder setText(int viewId,String text){
61         TextView tv = getView(viewId);
62         tv.setText(text);
63         return this;
64     }
65 
66     /**
67      * 设置图片
68      */
69     public ViewHolder setImageResource(int viewId,int drawableId){
70         ImageView iv = getView(viewId);
71         iv.setImageResource(drawableId);
72         return this;
73     }
74 
75     /**
76      * 设置图片
77      */
78     public ViewHolder setImageBitmap(int viewId, Bitmap bitmap){
79         ImageView iv = getView(viewId);
80         iv.setImageBitmap(bitmap);
81         return this;
82     }
83 
84     /**
85      * 设置图片
86      */
87     public ViewHolder setImageByUrl(int viewId,String url){
88         Picasso.with(context).load(url).into((ImageView) getView(viewId));
89 //        ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context));
90 //        ImageLoader.getInstance().displayImage(url, (ImageView) getView(viewId));
91         return this;
92     }
93 }

同步重新同步至Github:https://github.com/nanchen2251/CommonAdapterListViewDemo

posted @ 2016-07-28 21:28 南尘 阅读(...) 评论(...) 编辑 收藏

写不完的矫情,做不完的开源

点击进入我的GitHub页
南 尘
主 页
优美钢琴曲合集-南尘.mp3                    感谢您阅读我的博客,如果您现在工作、学习累了或者疲惫了,不妨聆听一下音乐,它能够减轻你的疲劳,还能够带给您一种舒适愉悦的心情。(样式取自博客园-欲泪成雪)