如果你去做任何一个项目,我相信你都会跟我有一样的经历,最最普遍的就是列表显示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
发表评论 取消回复