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

时间:2021-12-01 06:39:32

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

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

 package com.example.nanchen.commonadapterforlistviewdemo;

 import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView; import java.util.List; /**
* 常见的ListView的Adapter适配器
* Created by 南尘 on 16-7-28.
*/
public class MyListAdapter extends BaseAdapter {
private Context context;
private List<Data> list; public MyListAdapter(Context context, List<Data> list) {
this.context = context;
this.list = list;
} @Override
public int getCount() {
return list == null ? 0 : list.size();
} @Override
public Object getItem(int position) {
return list.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
MyViewHolder holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
holder = new MyViewHolder();
holder.iv = (ImageView) convertView.findViewById(R.id.item_image);
holder.tv = (TextView) convertView.findViewById(R.id.item_text);
convertView.setTag(holder);
} else {
holder = (MyViewHolder) convertView.getTag();
}
Data data = (Data) getItem(position);
holder.iv.setImageResource(data.getImageId());
holder.tv.setText(data.getText());
return convertView;
} public static class MyViewHolder {
ImageView iv;
TextView tv;
}
}

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

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

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

 public abstract class MyListAdapter<T> extends BaseAdapter {
private Context context;
private List<T> list; public MyListAdapter(Context context, List<T> list) {
this.context = context;
this.list = list;
} @Override
public int getCount() {
return list == null ? 0 : list.size();
} @Override
public Object getItem(int position) {
return list.get(position);
} @Override
public long getItemId(int position) {
return position;
}
}

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

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

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

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

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

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

我们尝试着封装一下ViewHolder

 public class ViewHolder {
//现在对于int作为键的官方推荐用SparseArray替代HashMap
private final SparseArray<View> views;
private int position;
private View convertView;
private Context context; private ViewHolder(Context context,ViewGroup parent, int layoutId, int position) {
this.context = context;
this.views = new SparseArray<>();
this.convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
convertView.setTag(this);
} /**
* 拿到一个ViewHolder对象
*/
public static ViewHolder get(View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return new ViewHolder(parent.getContext(),parent, layoutId, position);
}
return (ViewHolder) convertView.getTag();
} /**
* 通过控件的Id获取对于的控件,如果没有则加入views
*/
public <T extends View> T getView(int viewId) {
View view = views.get(viewId);
if (view == null) {
view = convertView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
}

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

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

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

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

封装后的ViewHolder是这样。

 package com.example.nanchen.commonadapterforlistviewdemo;

 import android.content.Context;
import android.graphics.Bitmap;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import com.squareup.picasso.Picasso; /**
* 万能适配器的ViewHolder
* Created by 南尘 on 16-7-28.
*/
public class ViewHolder {
//现在对于int作为键的官方推荐用SparseArray替代HashMap
private final SparseArray<View> views;
private int position;
private View convertView;
private Context context; private ViewHolder(Context context,ViewGroup parent, int layoutId, int position) {
this.context = context;
this.views = new SparseArray<>();
this.convertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
convertView.setTag(this);
} /**
* 拿到一个ViewHolder对象
*/
public static ViewHolder get(View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return new ViewHolder(parent.getContext(),parent, layoutId, position);
}
return (ViewHolder) convertView.getTag();
} /**
* 通过控件的Id获取对于的控件,如果没有则加入views
*/
public <T extends View> T getView(int viewId) {
View view = views.get(viewId);
if (view == null) {
view = convertView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
} public View getConvertView() {
return convertView;
} /**
* 设置字符串
*/
public ViewHolder setText(int viewId,String text){
TextView tv = getView(viewId);
tv.setText(text);
return this;
} /**
* 设置图片
*/
public ViewHolder setImageResource(int viewId,int drawableId){
ImageView iv = getView(viewId);
iv.setImageResource(drawableId);
return this;
} /**
* 设置图片
*/
public ViewHolder setImageBitmap(int viewId, Bitmap bitmap){
ImageView iv = getView(viewId);
iv.setImageBitmap(bitmap);
return this;
} /**
* 设置图片
*/
public ViewHolder setImageByUrl(int viewId,String url){
Picasso.with(context).load(url).into((ImageView) getView(viewId));
// ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context));
// ImageLoader.getInstance().displayImage(url, (ImageView) getView(viewId));
return this;
} public int getPosition(){
return position;
}
}

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

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

 package com.example.nanchen.commonadapterforlistviewdemo;

 import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter; import java.util.List; /**
* 打造ListView的万能适配器
* Created by 南尘 on 16-7-28.
*/
public abstract class CommonAdaper<T> extends BaseAdapter {
private Context context;
private List<T> list; public CommonAdaper(Context context, List<T> list) {
this.context = context;
this.list = list;
} @Override
public int getCount() {
return list == null ? 0 : list.size();
} @Override
public T getItem(int position) {
return list.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder = ViewHolder.get(view,viewGroup,R.layout.list_item,i);
convert(holder,getItem(i));
return holder.getConvertView();
} public abstract void convert(ViewHolder holder,T item); }

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

 package com.example.nanchen.commonadapterforlistviewdemo;

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView; import java.util.ArrayList;
import java.util.List; public class MainActivity extends AppCompatActivity { private ListView listView;
private List<Data> list; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.main_lv);
initList(); listView.setAdapter(new CommonAdaper<Data>(this,list) {
@Override
public void convert(ViewHolder holder, Data item) {
holder.setText(R.id.item_text,item.getText());
if (item.getImageUrl() != null){
holder.setImageByUrl(R.id.item_image,item.getImageUrl());
}else {
holder.setImageResource(R.id.item_image,item.getImageId());
}
}
});
} private void initList() {
list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new Data("本地 "+i,R.mipmap.ic_launcher));
} for (int i = 0; i < 5; i++) {
list.add(new Data("网络 "+i,"http://pic.cnblogs.com/face/845964/20160301162812.png"));
}
}
}

最后上我写的两个xml

一个是Activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.nanchen.commonadapterforlistviewdemo.MainActivity"> <ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_lv"/>
</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

 package com.example.nanchen.commonadapterforlistviewdemo;

 import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter; import java.util.List; /**
* 打造ListView的万能适配器
* Created by 南尘 on 16-7-28.
*/
public abstract class CommonAdaper<T> extends BaseAdapter {
private Context context;
private List<T> list;
private LayoutInflater inflater;
private int itemLayoutId; public CommonAdaper(Context context, List<T> list,int itemLayoutId) {
this.context = context;
this.list = list;
this.itemLayoutId = itemLayoutId;
inflater = LayoutInflater.from(context);
} @Override
public int getCount() {
return list == null ? 0 : list.size();
} @Override
public T 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 holder = getViewHolder(position,convertView,parent);
convert(holder,getItem(position));
return holder.getConvertView();
} public abstract void convert(ViewHolder holder,T item); private ViewHolder getViewHolder(int position,View convertView,ViewGroup parent){
return ViewHolder.get(context,convertView,parent,itemLayoutId,position);
} }

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

 package com.example.nanchen.commonadapterforlistviewdemo;

 import android.content.Context;
import android.graphics.Bitmap;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import com.squareup.picasso.Picasso; /**
* 万能适配器的ViewHolder
* Created by 南尘 on 16-7-28.
*/
public class ViewHolder {
//现在对于int作为键的官方推荐用SparseArray替代HashMap
private final SparseArray<View> views;
private View convertView;
private Context context; private ViewHolder(Context context,ViewGroup parent,int itemLayoutId,int position) {
this.context = context;
this.views = new SparseArray<>();
this.convertView = LayoutInflater.from(context).inflate(itemLayoutId,parent,false);
convertView.setTag(this);
} /**
* 拿到一个ViewHolder对象
*/
public static ViewHolder get(Context context,View convertView, ViewGroup parent, int layoutId, int position) {
if (convertView == null) {
return new ViewHolder(context,parent, layoutId, position);
}
return (ViewHolder) convertView.getTag();
} /**
* 通过控件的Id获取对于的控件,如果没有则加入views
*/
public <T extends View> T getView(int viewId) {
View view = views.get(viewId);
if (view == null) {
view = convertView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
} public View getConvertView() {
return convertView;
} /**
* 设置字符串
*/
public ViewHolder setText(int viewId,String text){
TextView tv = getView(viewId);
tv.setText(text);
return this;
} /**
* 设置图片
*/
public ViewHolder setImageResource(int viewId,int drawableId){
ImageView iv = getView(viewId);
iv.setImageResource(drawableId);
return this;
} /**
* 设置图片
*/
public ViewHolder setImageBitmap(int viewId, Bitmap bitmap){
ImageView iv = getView(viewId);
iv.setImageBitmap(bitmap);
return this;
} /**
* 设置图片
*/
public ViewHolder setImageByUrl(int viewId,String url){
Picasso.with(context).load(url).into((ImageView) getView(viewId));
// ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context));
// ImageLoader.getInstance().displayImage(url, (ImageView) getView(viewId));
return this;
}
}

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