自己之前对ListView是如何提升效率一直都是一知半解,懵懵懂懂,现在开始要看RecyclerView了,想要搞明白,不搞清楚就很难受
参考链接 :android配适器Listview的ViewHolder和ConvertView的用法和原理-CSDN博客
1. 效率提升
1.1 改进之前
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(@NonNull Context context, int textViewResourceId, @NonNull List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
// 重写getView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
}
上面的代码中FruitAdapter定义了一个主构造函数,用于将Activity的实例、ListView子项布局的id和数据源传递进来。另外又重写了getView()方法,这个方法**在每个子项被滚动到屏幕内的时候会被调用 **。 在getView()方法中,首先使用LayoutInflater来为这个子项加载我们传入的布局。 LayoutInflater的inflate()方法接收3个参数,前两个参数我们已经知道是什么意思了, 第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个 View添加父布局。因为一旦View有了父布局之后,它就不能再添加到ListView中了。接下来调用View的findViewById()方法分别获取到ImageView和 TextView的实例,然后通过getItem()方法得到当前项的Fruit实例,并分别调用它们的 setImageResource()和setText()方法设置显示的图片和文字,最后将布局返回,这样自定义的适配器就完成了。
1.2 改进之后
之所以说ListView这个控件很难用,是因为它有很多细节可以优化,其中运行效率就是很重要 的一点。目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法 中,在每个子项被滚动到屏幕内的时候会被调用,每次都将布局重新加载了一遍,即每次都会调用LayoutInflater加载视图类,当ListView快速滚动的时候,这就会成为性能的瓶颈。 仔细观察你会发现,getView()方法中还有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用。当 当前屏幕中的部分视图划出屏幕(即在当前屏幕中不可见的时候),安卓系统会将这些视图回收至缓存池中,我们可以借助这个参数来进行性能优化,即 最新滑进当前窗口的视图对象可以不用重新创建,而是复用缓存池中之前回收的视图。修改 FruitAdapter中的代码,如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> {
......
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
}else {
view = convertView;
}
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
可以看到,现在我们在getView()方法中进行了判断:如果convertView为null,即缓存池中没有可以复用的视图对象,则使用 LayoutInflater去加载布局;如果不为null,即缓存池中有之前回收的,现在可以复用的视图对象,则直接对convertView进行重用,就不用每次都调用LayoutInflater加载视图类。这样就大 大提高了ListView的运行效率,在快速滚动的时候可以表现出更好的性能。
2. ViewHolder
不过,目前我们的这份代码还是可以继续优化的,虽然现在已经不会再重复去加载布局,但是 每次在getView()方法中仍然会调用View的findViewById()方法来获取一次控件的实例,findViewByld要像遍历树型结构那样去遍历xml文件内容,所以非常消耗系统资源, 我们可以借助一个ViewHolder来对这部分性能进行优化,修改FruitAdapter中的代码,如 下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> {
......
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前位置的Fruit对象
View view;
ViewHolder viewHolder;
if (convertView == null) {
// 如果convertView为空,说明是新的视图,需要加载布局并初始化ViewHolder
view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
viewHolder = new ViewHolder();
// 初始化水果图片视图
viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
// 初始化水果名称视图
viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
view.setTag(viewHolder); // 将ViewHolder对象与视图关联
} else {
// 如果convertView不为空,说明是复用的视图,直接获取关联的ViewHolder
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
// 使用ViewHolder设置视图内容
viewHolder.fruitImage.setImageResource(fruit.getImageId()); // 设置水果图片
viewHolder.fruitName.setText(fruit.getName()); // 设置水果名称
return view; // 返回准备好的视图
}
class ViewHolder {
ImageView fruitImage;
TextView fruitName;
}
}
我们新增了一个内部类ViewHolder,用于对ImageView和TextView的控件实例进行缓存。当convertView为null的时候,创建一个 ViewHolder对象,并将控件的实例存放在ViewHolder里,这样后面复用的时候就不用再调用findViewById来查找了,然后调用View的setTag()方 法,将ViewHolder对象存储在View中。当convertView不为null的时候,则调用View的 getTag()方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了ViewHolder里, 就没有必要每次都通过findViewById()方法来获取控件实例了。