在写Android程序时,经常会用到ListView控件进行数据展示。
使用时,需要定义一个ListView并创建其Item布局,然后对ListView设置一个适配器adapter,一般继承自BaseAdapter,同时需要定义一个ViewHolder类存储控件。但是当有很多的ListView时,这种做法就比较麻烦了,我们可以发现其中有很多通用的代码。这时候就需要抽象出一个共同的适配器出来。
(一)ListView的用法
页面布局文件和Item布局文件
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=".MainActivity">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:divider="#d9d9d9"
android:dividerHeight="1dp">
</ListView>
</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="match_parent"
android:gravity="center_vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="8dp"
android:background="@drawable/wx" />
<TextView
android:id="@+id/item_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是ListView的Item布局"
android:textSize="18sp" />
</LinearLayout>
Adapter和ViewHolder
构建一个数据适配器MyBaseAdapter继承BaseAdapter,此处将MyBaseAdapter类设置成MainActivity的内部类,以便对一些成员进行直接调用。在编写一个ViewHolder类,包含Item中的两个子控件。
ViewHolder的作用:通过convertView.setTag(ViewHolder v)方法与convertView进行绑定,然后当convertView复用时,直接从与之对于的convertView.getTag()中拿到convertView布局中的控件,省去了findViewById的时间
public class MainActivity extends AppCompatActivity {
ListView mListView;
//需要适配的数据
String[] names = {"京东商城", "QQ", "QQ斗地主", "新浪微博", "天猫",
"UC浏览器", "微信"};
//图片集合
int[] icons = {R.drawable.jd, R.drawable.qq, R.drawable.dz,
R.drawable.xl, R.drawable.tm, R.drawable.uc, R.drawable.wx};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化ListView控件
mListView = (ListView) findViewById(R.id.lv);
//创建一个Adapter的实例
MyBaseAdapter mAdapter = new MyBaseAdapter();
//设置Adapter
mListView.setAdapter(mAdapter);
}
class MyBaseAdapter extends BaseAdapter {
//得到item的总数
@Override
public int getCount() {
//返回ListView Item条目的总数
return names.length;
}
//得到Item代表的对象
@Override
public Object getItem(int position) {
//返回ListView Item条目代表的对象
return names[position];
}
//得到Item的id
@Override
public long getItemId(int position) {
//返回ListView Item的id
return position;
}
//得到Item的View视图
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
//在第一次创建convertView时将控件找出,在第二次重用convertView时直接通过getTag()方法获得这些控件
if (convertView == null) {
convertView = LayoutInflater.from(getApplicationContext()).
inflate(R.layout.list_item,parent,false);
holder = new ViewHolder();
holder.mTextView = (TextView)convertView.findViewById(R.id. item_tv);
holder.imageView=(ImageView) convertView.findViewById(R.id.item_image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.mTextView.setText(names[position]);
holder.imageView.setBackgroundResource(icons[position]);
return convertView;
}
class ViewHolder {
TextView mTextView;
ImageView imageView;
}
}
运行结果:
(二)抽象一个通用的ListView的适配器
布局文件activity_main.xml文件和Item的布局list_item.xml同上。
Java Bean类
新建一个Java Bean类,用于描述ListView中每一个Item的信息
此例中每一个item包括一个文本、一个图片(用资源号表示)
public class Bean {
int pic; //对应Item的图片资源号
String text;//对应Item的文本
public Bean(int pic, String text) {
this.pic = pic;
this.text = text;
}
public int getPic() {
return pic;
}
public void setPic(int pic) {
this.pic = pic;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
通用的ViewHolder类
在上面的例子中,对于每一个ListView,都需要一个ViewHolder来存储Item中包含的控件,在通用的ViewHolder类中我们需要一个容器SparseArray(SparseArrays
map integers to Objects)存储一个id及其对应的view控件,我们再新建一个getView(int id),通过id获取到我们的控件即可。
同时可以为一些常用的控件设置属性值的方法。
public class ViewHolder {
//通用的ViewHolder类,提供一个容器,专门存每个Item布局中的所有控件
private final SparseArray<View> mViews;
//android提供的SparseArray类,和Map类似,但是比Map效率,不过键只能为Integer
//通过键值对保存Item中的所有控件,键为控件ID,值为控件引用
private View mConvertView;
private int mPosition;
private ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
this.mViews = new SparseArray<View>();
this.mPosition=position;
mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
//setTag
mConvertView.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
* 当需要拿这些控件时,通过getView(id)进行获取;
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public View getConvertView() {
return mConvertView;
}
// 为TextView设置字符串
public ViewHolder setText(int viewId, String text) {
TextView view = getView(viewId);
view.setText(text);
return this;
}
//为ImageView设置图片
public ViewHolder setImageResource(int viewId, int drawableId) {
ImageView view = getView(viewId);
view.setImageResource(drawableId);
return this;
}
//为ImageView设置图片
public ViewHolder setImageBitmap(int viewId, Bitmap bm) {
ImageView view = getView(viewId);
view.setImageBitmap(bm);
return this;
}
public int getPosition() {
return mPosition;
}
}
通用的Adapter类
构建一个通用的Adapter类,继承BaseAdapter类,指定其泛型,以便适应不同的Item。指定其为抽象类,以便在具体实现时设置不同控件的属性值
//编写通用的Adapter,指定泛型,以便适应不同的Java bean
public abstract class CommonAdapter<T> extends BaseAdapter {
protected LayoutInflater mInflater;
protected Context mContext;
protected List<T> mDatas;
protected final int mItemLayoutId;
public CommonAdapter(Context context, List<T> mDatas, int itemLayoutId) {
this.mContext = context;
this.mInflater = LayoutInflater.from(mContext);
this.mDatas = mDatas;
this.mItemLayoutId = itemLayoutId;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public T getItem(int position) {
return mDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = getViewHolder(position, convertView, parent);
convert(viewHolder, getItem(position));//getItem(position)的类型就是T,这句话在子类中的具体实现就是给具体的控件初始化
//并赋值,初始化赋值控件时需要viewHolder和具体的数据Java bean,在这里抽象出来就是类型T
return viewHolder.getConvertView();
}
public abstract void convert(ViewHolder helper, T item);
private ViewHolder getViewHolder(int position, View convertView, ViewGroup parent) {
return ViewHolder.get(mContext, convertView, parent, mItemLayoutId, position);
}
}
设置数据源初始化Java Bean
在MainActivity.java文件中设置数据源初始化Java Bean,在设置Adapter时实现CommonAdapter类的抽象方法,实现为不同的Item设置数据
public class MainActivity extends AppCompatActivity {
//需要适配的数据
String[] names = {"京东商城", "QQ", "QQ斗地主", "新浪微博", "天猫", "UC浏览器", "微信"};
//图片集合
int[] icons = {R.drawable.jd, R.drawable.qq, R.drawable.dz,
R.drawable.xl, R.drawable.tm, R.drawable.uc, R.drawable.wx};
List<Bean> mDatas;//数据源 每一个item中的数据 由names和icons组成
ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
listView = findViewById(R.id.lv);
listView.setAdapter(new CommonAdapter<Bean>(getApplicationContext(), mDatas, R.layout.list_item) {
@Override
public void convert(ViewHolder helper, Bean item) {
helper.setText(R.id.item_tv, item.getText());//为对应的TextView设置文本
helper.setImageResource(R.id.item_image, item.getPic());//为对应的ImageView设置图片
}
});
}
public void initData() { //初始化数据源
mDatas = new ArrayList<Bean>();
for (int i = 0; i < names.length; i++) {
Bean bean = new Bean(icons[i], names[i]);
mDatas.add(bean);
}
}
}
至此,成功构建了一个通用的ListView数据适配器模板,今后在使用ListView并进行数据适配时,编写与Item中对应的Java Bean,在ViewHolder类中编写设置所用控件属性值的方法,然后在Activity中实例化即可。