Android--ListView的数据错乱bug(adapter的优化)

声明

:本文为我在写一个点赞功能时候遇到的bug,借鉴了一篇博客,其中讲解了关于颜色错乱,头像错乱,checkbox错乱之类的东西,这里只是简单的从checkbox谈一下我对于这个adapter优化机制的理解。

1.ListView的Adapter

adapter的含义是适配器,是数据项与listView视图显示之间的桥梁

“An adapter manages the data model and adapts it to the individual rows in the list view. An adapter extends theBaseAdapter class.
The adapter would inflate the layout for each row in itsgetView() method and assign the data to the individual views in the row.
The adapter is assigned to theListView via thesetAdaptermethod on theListView object.”

adapter中最重要的方法非getView()莫属,listview每一行的显示都会调用getView()方法,通过getView()方法将每一行要显示的数据指定给相应的view。

第一种getView的写法:

这种方法在每次需要getView的时候就进行一次创建view,从xml布局文件里inflate的每一个view都会产生一个java对象:
View view = inflater.inflate(R.layout.item_testdisorderitem, null);
inlating 布局文件和创建java对象对时间和内存的消耗都是昂贵的。
除此之外,使用findViewById()方法也相对地耗时,对于一个的view对象来讲,首先它是一个面向对象元素,所以它是一个数据加上布局操作的对象,数据项就是我们对应的每个item子项数据,每个itemView其实都是一样的,只是更换了数据项而已,所以可以从一定程度的对View对象进行复用,减少内存时间的消耗。

 @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;  
}  

第二种getView的写法:

Android为了减少在时间和内存上的消耗,Android提供了convertView的参数来实现,当用户滚动屏幕的时候,一部分的item会从可视状态转变为不可视状态,进而可以被当前的可视行复用,举个简单的例子,如果屏幕中可见的行5行,当第一行滚出手机屏幕,第6行就可以复用第一行的java对象(view)
如果Android不能复用某行的话,Android System就会返回null给convertView。第一次显示列表的时候,屏幕中显示可见的列表项,这时候每行返回的convertView都是null。当向上滑动列表后,有些行被滑出屏幕,convertView(/rowView)不为空,因为有了可以复用的row。所以通过判断converView是否为空来处理何时复用,当convertView(/rowView)为空时才进行inflate xml file and create new java object,如果不为空,直接通过convertView(/rowView)来findViewById获取row里的各个view。
这里写图片描述

于是就成了下边的getView()

@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);  

        }  

        TextView brandChNameTv = (TextView) convertView.findViewById(R.id.item_chName_txt);  
        TextView brandEnNameTv= (TextView) convertView.findViewById(R.id.item_enName_txt);  

        //set data  

        return convertView;  
    }  

虽然减少了过度创建View的消耗,但是除了inlating 布局文件和创建java对象对时间和内存的消耗都是昂贵的。使用findViewById()方法也相对地耗时。我们知道每个列表项都会调用getView方法,如果执行上述的getView方法,那么每显示一个列表项rowView就要调用findViewById来查找各个view。多次findViewById不仅增加了时间消耗,也创建了更多的java对象,从而造成了expensive with regards to time and memory consumption。

第三种getView的写法:

Android为了解决这个问题也采取了相应的策略,使用viewHolder静态内部类,他可以持有布局文件相关的view的引用,通过使用setTag方法作为一个tag被指派给convertView,如果我们接受到一个convertView对象,我们可以使用getTag的方法获取viewHolder的实例,然后经由该引用指定新的属性,从而适配数据项的view。

最后形成了我们通常使用的写法

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;  
   } 

说了这么多,数据错乱在哪里?

诸如我们设置的item中的某个textView的text属性,在我们进行数据适配的时候都进行了相应的设置,所以在复用的时候并不会出现数据的错乱,但是在我们设置自主的设置了某一项的background属性的时候,因为我们设置的某个item在之后进行复用的时候并没有设置回原来的样子,所以就会出现颜色错乱,,,但我们这里指的是在item中嵌入类似checkBox之后所造成的数据错乱。
这里写图片描述

这里我们的右上角是一个点赞的按钮,radioButton,但是如果我们按照上边的方法进行数据项的导入后会发生错乱的问题,我们在进行点赞操作的时候,会惊讶的发现点击了某一个数据项,但是在后台上,却显示点击了其他的数据项,给其他的文章进行了点赞,那么这是什么原因呢?

我个人的想法是:由于view的复用,我们的数据项对view虽然进行了设置,文字等都是好的,但是与checkbox相关联的却是其他的数据项,在进行复用的时候,产生了不可预料的数据项偏移,也就是checkbox和当前的textView好像在为不同的数据项服务,解决这个问题,我们可以在处理listView中包含有checkbox,toggleButton之类的控件时候,需要对于控件状态进行监控,通过setTag将checkbox和相应的对象绑定在一起,一旦状态发生改变,就需要在监听方法中使用getTag将对象取出,修改数据项。

方法如下:

package com.aliao.myandroiddemo.adapter;  

import android.content.Context;  
import android.util.Log;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.BaseAdapter;  
import android.widget.CheckBox;  
import android.widget.CompoundButton;  
import android.widget.ImageView;  
import android.widget.TextView;  

import com.aliao.myandroiddemo.R;  
import com.aliao.myandroiddemo.domain.BrandItemInfo;  

import java.util.List;  

/** 
 * Created by liaolishuang on 14-3-31. 
 */  
public class TestDisorderListAdapter extends BaseAdapter{  

    private Context context;  
    private List<BrandItemInfo> brandInfoList;  
    private final String TAG = "disorderlist";  

    public TestDisorderListAdapter(Context context, List<BrandItemInfo> list){  

        this.context = context;  
        brandInfoList = list;  

    }  

    @Override  
    public int getCount() {  
        return brandInfoList.size();  
    }  

    @Override  
    public Object getItem(int i) {  
        return null != brandInfoList?brandInfoList.get(i):null;  
    }  

    @Override  
    public long getItemId(int i) {  
        return i;  
    }  

    private class ViewHolder{  
        private TextView brandEnNameTv;  
        private TextView brandChNameTv;  
        private CheckBox followCheckBox;  
        private ImageView brandLogo;  
    }  


    @Override  
    public View getView(int i, View view, ViewGroup viewGroup) {  

        ViewHolder viewHolder = null;  
        BrandItemInfo brandItemInfo = (BrandItemInfo) getItem(i);  

        if(null == view){  

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
            view = inflater.inflate(R.layout.item_testdisorderitem,null);  

            viewHolder = new ViewHolder();  
            viewHolder.brandChNameTv = (TextView) view.findViewById(R.id.item_chName_txt);  
            viewHolder.brandEnNameTv = (TextView) view.findViewById(R.id.item_enName_txt);  
            viewHolder.brandLogo = (ImageView) view.findViewById(R.id.item_brandLogo_imagev);  
            viewHolder.followCheckBox = (CheckBox) view.findViewById(R.id.item_follow_checkbox);  
            final ViewHolder finalViewHolder = viewHolder;  
            viewHolder.followCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {  
                @Override  
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {  
                    BrandItemInfo info = (BrandItemInfo) finalViewHolder.followCheckBox.getTag();  
                    info.setSelected(compoundButton.isChecked());  
                }  
            });  
            view.setTag(viewHolder);  
            viewHolder.followCheckBox.setTag(brandItemInfo);  

        }else {  

            viewHolder = (ViewHolder) view.getTag();  
            viewHolder.followCheckBox.setTag(brandItemInfo);  
        }  

        viewHolder.brandChNameTv.setText(brandItemInfo.getBrandChName());  
        viewHolder.brandEnNameTv.setText(brandItemInfo.getBrandEnName());  
        viewHolder.brandLogo.setImageResource(brandItemInfo.getBrandImage());  
        viewHolder.followCheckBox.setChecked(brandItemInfo.isSelected());  

        return view;  
    }  

} 

最后bug解决了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值