名词介绍
ListView我们称之为列表,ListView中显示的每列,我们称之为列表项。
内容:
本文讲列表项View复用,那么何时会复用列表项View?
当列表存在滚动时,即所有列表项不能被全部看到,就会复用View。
本文以ListView使用时重写BaseAdapter的适配器为例,讲解列表项View复用中的ViewHolder模式实现的控件对象复用。
在重写BaseAdapter时,通过重写getView方法实现自定义列表项View和列表项View复用,每个列表项的每次展示都需要调用getView方法,getView方法返回值和参数如下。
public View getView(int position, View convertView, ViewGroup parent)
接下来会通过对getView3种不同写法的对比来讲解ViewHolder模式实现的列表项View复用中的控件对象复用。
在此我们将列表项View分为两种,一种是布局复用(即xml文件),一种是控件复用(Button、TextView、ImageView等基本控件),此文例子中的控件是TextView。
依据这两种View,本文中的3种写法分别对应如下:
- 不复用布局和控件
- 复用布局(即getView方法参数中的View convertView)
- 复用布局和控件(即TextView,通过ViewHolder模式实现)
第一种写法,不复用View
class Adapter1 extends BaseAdapter {
ArrayList<String> data;
Context context;
public Adapter1(ArrayList<String> data, Context context) {
this.data = data;
this.context = context;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(R.layout.text_row_item, null);
TextView tv = view.findViewById(R.id.textView);
tv.setText(data.get(position));
return view;
}
}
因为在列表滚动时每次看到不同的列表项都会调用getView方法,上面的getView方法在每次调用getView时都会生成一个新的布局View和TextView控件对象,在不断滚动列表时,会不断生成新的布局View和TextView,会使内存持续增加。
第二种写法,复用布局(即getView方法中的View convertView)。
class Adapter2 extends BaseAdapter {
ArrayList<String> data;
Context context;
//用于查看布局View的个数
int i = 1;
public Adapter2(ArrayList<String> data, Context context) {
this.data = data;
this.context = context;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.text_row_item, null);
Log.i("Adapter2","convertView"+i);
i++;
}
TextView tv = convertView.findViewById(R.id.textView);
tv.setText(data.get(position));
return convertView;
}
}
布局View的复用,是ListView实现的,实现的原理是当列表项View在屏幕中不可见时此列表项View即可被复用,此复用原理不在本文中讲述,通过判断getView方法的View参数是否为空,来判断是否需要新生成布局View,以此方法来复用View,减少布局View对象的生成,减少内存的使用,因生成新的View是IO操作,因此也可以提高访问速度。
第三种写法,复用布局和控件(即TextView,通过ViewHolder模式实现)
class Adapter3 extends BaseAdapter {
ArrayList<String> data;
Context context;
//用于查看布局View的个数
int i = 1;
public Adapter3(ArrayList<String> data, Context context) {
this.data = data;
this.context = context;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.text_row_item, null);
viewHolder = new ViewHolder();
viewHolder.textView = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(viewHolder);
Log.i("Adapter3","convertView+TextView"+i);
i++;
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(data.get(position));
return convertView;
}
class ViewHolder{
TextView textView;
}
}
与第二中写法相比,此写法增加了一个内部类ViewHolder,此类名可自定义,不是必须要写为ViewHolder,ViewHolder类中有一个TextView对象,此类是用于存储布局View中的控件(TextView)的对象的。
在新生成布局View的时候,新建一个ViewHolder对象,然后获取控件(TextView)的对象并将其存储在ViewHolder对象中,然后将ViewHolder对象通过布局View的setTag()方法存储在布局View中。
当复用布局View的时候,不用重新获取控件(TextView)对象,直接把通过布局View的setTag方法存储的ViewHolder对象,通过布局View的getTag方法获取,即可通过ViewHolder对象使用控件(TextView)对象,以此来实现控件(TextView)的复用,不用每次调用getView时都生成新的控件对象,再次减少内存的使用,因获取控件对象操作也是IO操作,也可再次提高访问速度。
第三种写法的实现方式,我们称之为ViewHolder模式。