$ListView的优化机制和滑动时数据错乱的讨论
Refer:http://www.myexception.cn/mobile/1612364.html
(一)Android ListView的基本用法
1、创建一个实体类Person,为其添加Getter和Setter方法,作为ListView适配器的类型:
public class Person {
private int imageId;
private String name;
private int age;
public Person(int imageId, String name, int age) {
this.imageId = imageId;
this.name = name;
this.age = age;
}
public int getImageId() {
return imageId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
2、创建person_item.xml文件,其中包含一个ImageView和两个TextView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/person_item_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" > <ImageView
android:id="@+id/image_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/img" /> <LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center" > <TextView
android:id="@+id/name_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Tom" /> <TextView
android:id="@+id/age_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="20" />
</LinearLayout> </LinearLayout>
3、创建自定义适配器类PersonAdapter,以Person类为泛型,继承自ArrayAdapter<Person>,重写父类的构造方法和getView方法,getView方法会在每个子项被滚动到屏幕内的时候调用:
public class PersonAdapter extends ArrayAdapter<Person> {
private int mResourceId;
public PersonAdapter(Context context, int textViewResourceId,
List<Person> objects) {
super(context, textViewResourceId, objects);
// textViewResourceId:ListView子项布局的id;objects:数据
mResourceId = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 1.获取当前项的Person实例
Person person = getItem(position);
// 2.为这个子项加载传入的布局
View view = LayoutInflater.from(getContext()).inflate(mResourceId, null);
// 3.用view的findViewById方法获取到子项布局控件的实例
ImageView imgIv = (ImageView) view.findViewById(R.id.image_iv);
TextView nameTv = (TextView) view.findViewById(R.id.name_tv);
TextView ageTv = (TextView) view.findViewById(R.id.age_tv);
// 4.设置相应控件的内容
imgIv.setImageResource(person.getImageId());
nameTv.setText(person.getName());
ageTv.setText(person.getAge() + "");
// 5.为imgIv设置点击事件,点击它的时候换图片
final ImageView finalImgIv = imgIv;
imgIv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
finalImgIv.setImageResource(R.drawable.another_img);
}
});
// 6.返回view
return view;
}
}
4、activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <ListView
android:id="@+id/person_info_lv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView> </LinearLayout>
5、MainActivity:
public class MainActivity extends Activity {
private ListView personInfoLv;
private String[] names;
private int[] ages;
private List<Person> persons;
private int imageId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
personInfoLv = (ListView) findViewById(R.id.person_info_lv);
names = new String[] { "AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG",
"HHH", "III", "JJJ", "KKK", "LLL", "MMM", "NNN", "OOO" };
ages = new int[names.length];
persons = new ArrayList<Person>();
imageId = R.drawable.img;
for (int i = 0; i < names.length; i++) {
ages[i] = i + 1;
}
// 创建Person信息列表
for (int i = 0; i < names.length; i++) {
Person person = new Person(imageId, names[i], ages[i]);
persons.add(person);
}
// 创建adapter
PersonAdapter adapter = new PersonAdapter(MainActivity.this,
R.layout.person_item, persons);
// 设置adapter
personInfoLv.setAdapter(adapter);
}
}
运行效果:

(二)ListView的性能优化及滑动时数据显示错乱问题解决
1、在adapter的getView方法中,每次都将布局重新加载一遍,当快速滚动屏幕时候就会带来性能问题;此外,View的findViewById方法对性能的影响也比较大。为此要做一些优化,主要使用缓存和ViewHolder两种策略。缓存机制如下图,可以实现item的复用(假设一屏可以容纳7个item)

2、假设现在有两个新需求:一个是把列表的前三项的背景颜色设置成蓝色的,另一个是在每次点击每一item的图片时,不仅要修改图片,还要把修改后的图片id存到列表对象中去,这个可以用控件的setTag方法来实现。加上实现优化策略,最终修改原adapter如下:
public class PersonAdapter extends ArrayAdapter<Person> {
private int mResourceId;
public PersonAdapter(Context context, int textViewResourceId,
List<Person> objects) {
super(context, textViewResourceId, objects);
mResourceId = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Person person = getItem(position);
View view;
ViewHolder viewHolder;
if (null == convertView) {
view = LayoutInflater.from(getContext()).inflate(
R.layout.person_item, null);
viewHolder = new ViewHolder();
viewHolder.imageIv = (ImageView) view.findViewById(R.id.image_iv);
viewHolder.nameTv = (TextView) view.findViewById(R.id.name_tv);
viewHolder.ageTv = (TextView) view.findViewById(R.id.age_tv);
// 点击图片的时候更换图片,并更改列表对象中的imageId的值
final ViewHolder finalViewHolder = viewHolder;
viewHolder.imageIv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Person p = (Person) finalViewHolder.imageIv.getTag();
int currentImageId;
if (p.getImageId() == R.drawable.img) {
finalViewHolder.imageIv
.setImageResource(R.drawable.another_img);
currentImageId = R.drawable.another_img;
} else {
finalViewHolder.imageIv
.setImageResource(R.drawable.img);
currentImageId = R.drawable.img;
}
p.setImageId(currentImageId);
}
});
view.setTag(viewHolder);
viewHolder.imageIv.setTag(person);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
viewHolder.imageIv.setTag(person);
}
viewHolder.imageIv.setImageResource(person.getImageId());
viewHolder.nameTv.setText(person.getName());
viewHolder.ageTv.setText(person.getAge() + "");
// 为前三个item设置背景颜色为蓝色
if (position < 3) {
view.setBackgroundColor(0xFF0000FF);
}
return view;
}
class ViewHolder {
ImageView imageIv;
TextView nameTv;
TextView ageTv;
}
}
3、这时发现在滑动ListView后,不仅是前三个item的背景颜色是蓝色的,而且后面有些项的背景颜色也变成了蓝色的,而且毫无规律可循。滑动几次后的效果如下图所示:

这主要是因为缓存复用引起的问题,只需要在原来代码的64行后面添加else判断即可,将不是前三行的item的背景颜色设置成默认的白色的。最终代码如下:
public class PersonAdapter extends ArrayAdapter<Person> {
private int mResourceId;
public PersonAdapter(Context context, int textViewResourceId,
List<Person> objects) {
super(context, textViewResourceId, objects);
mResourceId = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Person person = getItem(position);
View view;
ViewHolder viewHolder;
if (null == convertView) {
view = LayoutInflater.from(getContext()).inflate(
R.layout.person_item, null);
viewHolder = new ViewHolder();
viewHolder.imageIv = (ImageView) view.findViewById(R.id.image_iv);
viewHolder.nameTv = (TextView) view.findViewById(R.id.name_tv);
viewHolder.ageTv = (TextView) view.findViewById(R.id.age_tv);
// 点击图片的时候更换图片,并更改列表对象中的imageId的值
final ViewHolder finalViewHolder = viewHolder;
viewHolder.imageIv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Person p = (Person) finalViewHolder.imageIv.getTag();
int currentImageId;
if (p.getImageId() == R.drawable.img) {
finalViewHolder.imageIv
.setImageResource(R.drawable.another_img);
currentImageId = R.drawable.another_img;
} else {
finalViewHolder.imageIv
.setImageResource(R.drawable.img);
currentImageId = R.drawable.img;
}
p.setImageId(currentImageId);
}
});
view.setTag(viewHolder);
viewHolder.imageIv.setTag(person);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
viewHolder.imageIv.setTag(person);
}
viewHolder.imageIv.setImageResource(person.getImageId());
viewHolder.nameTv.setText(person.getName());
viewHolder.ageTv.setText(person.getAge() + "");
// 为前三个item设置背景颜色为蓝色
if (position < 3) {
view.setBackgroundColor(0xFF0000FF);
} else {
view.setBackgroundColor(0xFFFFFFFF);
}
return view;
}
class ViewHolder {
ImageView imageIv;
TextView nameTv;
TextView ageTv;
}
}
总结:总之防止错乱关键就是一句话:哪里对控件有修改,另外的地方就要把它改回来。
随机推荐
- [Android]生成heap dump文件(.hprof)
Android生成heap dump文件(.hprof) 一个heap dump就是一个程序heap的快照,能够获知程序的哪些部分正在使用大部分的内存. 它保存为一种叫做HPROF的二进制格式.对于A ...
- web开发中经常使用的js
将自己在web开发中经经常使用到的一些JS总结一下. 1.改动标签和表单的值 改动标签的值: var customer = document.getElementById("custm&qu ...
- Codeforces Round #404 (Div. 2) DE
昨晚玩游戏竟然不小心错过了CF..我是有多浪啊. 今天总算趁着下课时间补了,感觉最后两题还是挺有意思的,写个题解. D: 题目大意: 给出一个括号序列,问有多少个子序列 是k个'(' + k个')' ...
- xxxxxxclub系统模块分类
不是分析整个程序执行的过程. 分析程序在设计的时候模块怎样分类 针对的是应用程序,name 类的装载:1. Spring配置 基于接口调用hsf 3. 一个页面相应的java类 Spring的xml文 ...
- 这篇文章主要为大家详细介绍了jQuery密码强度验证控件使用详解的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文实例为大家分享了jQuery密码强度验证控件,供大家参考,具体内容如下 <html> <head> <meta http-equiv="Cont ...
- ubuntu在vmware下使用问题
问题1: 在vmware虚拟机下安装的ubuntu系统,为了在windows和ubuntu之间拷贝数据方便,所以在vmware上安装了vmware tools.在安装了vmware tool之后会在u ...
- Apache Nifi 开发
Apache NiFi是由美国过国家安全局(NSA)贡献给Apache基金会的开源项目.其设计目标是自己主动化系统间的数据流.基于其工作流式的编程理念.NiFi很易于使用,强大.可靠及高可配置. 两个 ...
- 谈抽象1——无脑copy等于自杀
近期被外派帮助国内某公司做政府某部门OA系统.听说他们那有个成熟的java框架,使用了非常长时间,抱着学习的态度,我进入这个公司.当我熟悉了一周后,留下了非常多疑问,而这些疑问,也诱发了这次关于&qu ...
- [转]Linux Socket编程 Socket抓取网页源码
“一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览 ...
- 【BZOJ1280】Emmy卖猪pigs 最大流
[BZOJ1280]Emmy卖猪pigs Description Emmy在一个养猪场工作.这个养猪场有M个锁着的猪圈,但Emmy并没有钥匙.顾客会到养猪场来买猪,一个接着一个.每一位顾客都会有一些猪 ...