Getting Start
遇到过的ListView在滑动时数据错乱的几种情况:
- Listview滑动后,图片(/背景色)重复混乱(非异步加载时)
- Listview选取checkbox后,再滑动时,出现checkbox选取错位问题
- ListView异步加载图片时,图片显示重复错乱
这里所说的"重复混乱"是指:在滑动list的时候,会看到某行本不该显示却重复显示了其他行的数据(根据情况的不同,数据可以是文本,checkbox的选中状态,图片,背景色等等...),而之所以让人感觉到混乱或者说错乱是因为这些item的重复现象有时候看似没有什么规律可寻。
在进行问题重现之前,先有必要对处理数据与视图显示的adapter类以及ViewHolder模式进行深入理解:
Adapters and Holder Pattern
adapter是数据与listview视图显示之间的桥梁:
“An adapter manages the data model and adapts it to the individual rows in the list view. An adapter extends the
BaseAdapter
class.The adapter would inflate the layout for each row in its
getView()
method and assign the data to the individual views in the row.The adapter is assigned to the
ListView
via thesetAdapter
method on theListView
object.”adapter中最重要的方法非getView()莫属,listview每一行的显示都会调用getView()方法,通过getView()方法将每一行要显示的数据指定给相应的view。
getView()通常的写法如下:
- private class ViewHolder{
- private TextView brandEnNameTv;
- private TextView brandChNameTv;
- private CheckBox followCheckBox;
- }
- @Override
- public View getView(int i, View convertView, ViewGroup viewGroup) {
- ViewHolder viewHolder = null;
- if(null == convertView){
- LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.item_testdisorderitem,null);
- viewHolder = new ViewHolder();
- viewHolder.brandChNameTv = (TextView) convertView.findViewById(R.id.item_chName_txt);
- viewHolder.brandEnNameTv= (TextView) convertView.findViewById(R.id.item_enName_txt);
- convertView.setTag(viewHolder);
- }else {
- viewHolder = (ViewHolder) convertView.getTag();
- }
- //set data
- return convertView;
- }
代码片段1.1private class ViewHolder{ private TextView brandEnNameTv; private TextView brandChNameTv; private CheckBox followCheckBox; } @Override public View getView(int i, View convertView, ViewGroup viewGroup) { ViewHolder viewHolder = null; if(null == convertView){ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.item_testdisorderitem,null); viewHolder = new ViewHolder(); viewHolder.brandChNameTv = (TextView) convertView.findViewById(R.id.item_chName_txt); viewHolder.brandEnNameTv= (TextView) convertView.findViewById(R.id.item_enName_txt); convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder) convertView.getTag(); } //set data return convertView; }
以上这种写法listview进行了优化,对比与以下这种方式:
- @Override
- public View getView(int i, View view, ViewGroup viewGroup) {
- LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View rowview = inflater.inflate(R.layout.item_testdisorderitem,null);
- TextView brandChNameTv = (TextView) rowview.findViewById(R.id.item_chName_txt);
- TextView brandEnNameTv= (TextView) rowview.findViewById(R.id.item_enName_txt);
- //set data
- return rowview;
- }
代码片段1.2@Override public View getView(int i, View view, ViewGroup viewGroup) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rowview = inflater.inflate(R.layout.item_testdisorderitem,null); TextView brandChNameTv = (TextView) rowview.findViewById(R.id.item_chName_txt); TextView brandEnNameTv= (TextView) rowview.findViewById(R.id.item_enName_txt); //set data return rowview; }
具体是怎么优化的以及优化了什么呢?
从xml布局文件里inflate的每一个view都会产生一个java对象(eg:View view):
View view = inflater.inflate(R.layout.item_testdisorderitem, null);
inlating 布局文件和创建java对象对时间和内存的消耗都是昂贵的。
除此之外,使用findViewById()方法也相对地耗时。
为了让listview减少在时间和内存上的消耗,Android提供了convertView参数(getView方法的第二个参数,不一定都叫convertView,个人认为叫rowView更好)来实现这一优化。当用户滑动列表时,原先可视的item被滚出屏幕变得不可视,而代表该行的java对象可以被新的可视行复用。也就是说如果列表在手机屏幕中一屏可见的行有7行,当第一行滑出屏幕时,底部新滑出来的第8行可以复用第1行的java对象(即通过item布局inflate出来的view),Android已经把第一行的布局缓存起来,作为可以复用的rowview: