最佳实践
1、将【内部类】用 static修饰一下。public static class ViewHolder { }
2、将实例mViewHolder作为外部类的成员变量。private ViewHolder mViewHolder;
注解:
- ViewHolder是否用static修饰,结果都是一样的,效率也没什么提升,不过大家都习惯用 static修饰一下。
- 一种情况下可以将mViewHolder作为局部变量,那就是想【临时】操作屏幕上item中的控件,这时屏幕上的mViewHolder和convertView是一一对应的,可以方便的通过mViewHolder获取你所操作的convertView中的所有控件,进而进行各种操作。但是一定要谨记,这种操作是临时的,下次进入界面时又会恢复原来的状态。要实现永久存储,只有一个办法,那就是将你所操作的结果保存在listview的adapter中的集合中,既然是操作集合,那肯定是通过point获取、操作控件了。
- 所以,根据需求,若是想临时修改控件属性,就将mViewHolder作为局部变量,若是想持久修改控件属性,就采取将状态保存在集合中的方式。
View重用的原理探讨
1. View的重用的原理
- View的每次创建是比较耗时的,因此对于 getview 方法传入的 convertView 应充分利用 != null 的判断。
- 如果你有N个 item,其中只有可见的 item +1 个(即 item1 - item8 )存在内存中,其他的在 Recycler(循环器)中。
- ListView先一个一个的调用 getView 创建所有可见的 item 的视图,此时getView中的参数 convertView 是空 null 的。
- 当 item1 滚出屏幕,并且一个新的 item8 从屏幕底端滚上来时(只要有一点点露头就算滚上来了),ListView再调用 getView 请求一个 item9 的视图时,convertView 不是空值了,它的值就是刚刚滚出去的 item1。
- 所以这时你只需将 item9 的【数据】赋给 convertView 然后返回 convertView 即可得到 item9 的视图,而不必重新 new 一个视图。
2、ViewHolder的应用
- View的 findViewById 方法也是比较耗时的,因此需要考虑只调用一次,之后就用 View.getTag() 方法来获得ViewHolder对象。我们知道,每个item都有同样的【视图结构】,item之间仅仅是所填充的数据不同,若每次都为得到一个相同结构的视图都要重新 inflate 布局文件,然后 findViewById 布局中的控件,这是很浪费资源的,为了不重复 findViewById,我们可以定义一个 ViewHolder 用于封装所有需要更新数据的控件,那么以后就可以直接赋值。
- ViewHolder只是将需要缓存的那些view封装好,convertView的setTag才是将这些缓存起来供下次调用。View中的 setTag 方法表示给View添加一个额外的数据,以后可以用getTag()将这个数据取出来。
代码
现在大家都知道用ViewHolder来实现listview的优化了,但是,ViewHolder到底要用什么来修饰,ViewHolder的实例到底应该放在哪呢?为了弄清楚这个问题,我做了以下测试。
我简单说一下代码是什么意思,ViewHolder有一个成员变量id,用来区分不同的ViewHolder,在构造函数中,对id进行赋值,itemId是一个静态变量,每初始化一次就+1,我们可以根据构造函数的打印次数,来计算ViewHolder的实例化次数,根据toString()可以来判断到底是使用了哪一个ViewHolder。getVIew中的写法是固定的,下面是测试结果:public class MainActivity extends ListActivity {
//ViewHolder mViewHolder;
//作为成员变量,首先调用10次 getView(),并且每调用一次,就对【此实例】重新初始化一次(new)
//虽然new了10次,但至始自终都是指向的是同一个地址,并且这个ViewHolder 始终只代表【最后出现】的那个条目
public static int itemId = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getListView().setAdapter(new MyAdapter());
}
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return 100;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder mViewHolder; //或者ViewHolder mViewHolder;
//若作为局部变量,初始化时会先调用10次 getView(),每调用一次,就 会new一个新的 ViewHolder
//所以共有10个 ViewHolder 实例,每个 ViewHolder 实例代表一个【当前屏幕】上的一个条目
//注意:final修饰引用类型变量时,只是保证这个引用的地址不变,即一直引用同一对象,但这个对象自身是可以改变的
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.item, parent, false);
mViewHolder = new ViewHolder();
mViewHolder.tv = (TextView) convertView.findViewById(R.id.tv);
convertView.setTag(mViewHolder);//标记
} else mViewHolder = (ViewHolder) convertView.getTag();
mViewHolder.tv.setText("listview条目的序号=" + position + ",mViewHolder的id=" + mViewHolder.id);
//不管采用的是哪种方法,在次操作item中的控件都没有任何问题!千万别忧虑!
Log.d("bqt", "position=" + position + " " + mViewHolder.id + " " + mViewHolder.hashCode());
return convertView;
}
}
private class ViewHolder {//ViewHolder用private、private static、private final或private static final修饰时完全一样!
//结论:如果界面内可见的item是9个,那么不管哪种方式,自始至终ViewHolder一共初始化了9+1次
//之后都是复用之前初始化的ViewHolder,但并不能保证哪个item复用的是哪个ViewHolder的实例。
private int id;//内部类的任何成员都是完全暴露给外部类的
private TextView tv;
private ViewHolder() {
id = itemId;
itemId++;
}
}
}
item的布局
item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="62dp"
android:gravity="center_vertical"
android:text="listview的每个条目" />
实验一,如何修饰内部类ViewHolder
结论1:如果界面内可见的item数量是9个,那么自始至终ViewHolder一共初始化了9+1次,之后都是复用ViewHolder,但并不保证哪个item复用的是哪个ViewHolder的实例。
结论2:内部类 ViewHolder 不管是用 private 修饰,还是用 private static 修饰,还是用 private final 修饰,还是用 private static final class ViewHolder修饰,完全一样!
static修饰类,在这里是静态内部类,并不是说只存在一个实例,而是可以访问外部类的静态变量,final修饰类则是不让该类继承,我们这里使用final毫无根据,所以,以后写ViewHolder的时候,可以不纠结了,加什么加啊,什么都不用加!
实验二,ViewHolder的实例放在哪里
结论:
mViewHolder无论是作为activity的成员变量,还是作为getView()的局部变量,还是作为getView()的 final 局部变量,当然mViewHolder不能作为final的成员变量,上面的结论依然成立。
个人理解的区别:
- 1、作为成员变量,首先调用10次 getView(),那么每调用一次,就初始化一次,虽然 new 了10次,但至始自终只有一个 ViewHolder 实例,并且这个ViewHolder 始终只代表最后出现的那个条目
- 2、作为局部变量,首先调用10次 getView(),很明显,每调用一次,就 new了一个新的 ViewHolder,所以共有10个 ViewHolder 实例,每个 ViewHolder 实例代表一个当前屏幕上的一个条目
- 3、所以,主要区别在于,作为成员变量,只有一个对象,始终代表最后出现的item;作为局部变量,共有10个对象,代表屏幕上的每个item。