解决ListView滚动复用convertview和ViewHolder数据填充错乱

 

我说下我的理解,最可能出现重复的情况就是getView(int position, View convertView, ViewGroup parent)中的convertview利用的情况,由于getview的时候,listview自身会复用已存在的item,即重用最先新建的那几个item,还有就是注意tag的使用,convertView.getTag()返回的也是重用的view,其状态是和被重用的一样,包括图片的显示与隐藏的状态,进度条的刷新等,都会被重用,这就出现了图片或者进度条错乱的情况,这时就需要在getview的时候,先把状态复原(即隐藏进度条,去掉没有下载的item的监听,图片设为默认等等),然后再根据判断条件修改各个item的状态。


 

这些天用到了ListView,由于要用到ImageView,且图片源不是在资源里面的,也就没法用到ID了,也就不能用SimpleAdapter之类的了。只能自己改写一个Adapter,直接继承BaseAdapter。由于一开始只是在网上看了一下如何写getView这个方法,根本没有去进一步理解各个参数的含义(当然现在也没有全理解。。。),一样一来,运行没问题了,ListView里面数据也有了,结果来了个Bug,滚动的时候有些地方会重复前面出现过的图片(或者说是第一页出现去的图片)。

正常情况下的getView方法体

public View getView(int position, View convertView, ViewGroup parent){

}

  里面比较纠结的就是View convertView。

  converView就是ListView里面每条记录(Item)的样式布局。

  在ListView里面每显示一条记录就会记录就会调用一次getView。但是为了优化速度,它只会缓存当前屏幕所显示的记录的View。这个可以在getView里面加一个输出语句,看getView什么时候执行,执行过多少次。就明白了。

private LayoutInflater mInflater; 
       @Override
        public View getView(int position, View convertView, ViewGroup parent) {      
            // TODO Auto-generated method stub 
            ViewHolder  myViews; 
            if (convertView == null){ 
                System.out.println("为空:" + position);            
                myViews = new ViewHolder (); 
                convertView = mInflater.inflate(R.layout.lst_item, null); 
                myViews.mNameText = (TextView) convertView.findViewById(R.id.clst); 
                myViews.mPhoto = (ImageView) convertView.findViewById(R.id.mphoto); 
                convertView.setTag(myViews); 
            } 
            else { 
                myViews = (ViewHolder ) convertView.getTag(); 
                System.out.println("不为空:" + position); 
            } 
              
            Info p = infoList.get(position); 
            String dn = p.getDisplayName; 
         If (!dn== null){          
          myViews.mNameText.setText(dn); 
        } 
        Bitmap bm = p.getPhoto(); 
        If(!p == null){           
          myViews.mPhoto.setImageBitmap(bm); 
        }  
         //myViews.mNameText.setText(dn); 
            //Bitmap bm = p.getPhoto(); 
            //myViews.mPhoto.setImageBitmap(bm);                 
            return convertView; 
        } 
      
        static class ViewHolder { 
            private TextView mNameText; 
            private ImageView mPhoto; 
      } 


回到问题上来:

  出现重复内容,基本上都是使用了ViewHolder这种方法的。

  当我们判断 convertView == null  的时候,如果为空,就会根据设计好的ListItem布局(XML),来为convertView赋值,并生成一个viewHolder来绑定converView里面的各个View控件(XML布局里面的那些控件)。再用convertViewsetTagviewHolder设置进去。

如果convertView不为空的时候,就会直接用convertViewgetTag(),来获得一个ViewHolder

  后面就是对ViewHolder里面那些控件来进行设置,比如显示文字,显示图片什么的了。

  如果再接下来的设置中,有某些条记录的某些控件没有被赋值,比如TextView因为要设置的内容为空,或者ImageView因为图片为空就没有赋值,而是直接跳过了。就类似下面这种。

String t = XXX.getName; 
Bitmap p = XXX.getPhoto; 
If (!t == null){ 
mViewHolder.nameText.setText(t); 
} 
If(!p == null){ 
mViewHolder.photoView.setImageBitmap(p); 
} 


前面说过,ListView只会缓存第一屏里面的List Item的视图布局,之后滚动ListView后面的Item的布局就都是用前面所缓存的视图布局(也就是convertView不为null)。这样如果当后面的某一条记录里面的某些控件因上面的原因没有赋值,就会直接使用前面所缓存的视图里面的值了(里面有值的话)。

  这样的最终效果就是,滚动的时候,会出现前面已经出现过的内容。

 

  最简单的解决方法就是,在上面的代码中不去判断赋值内容是否为空,而是直接设定对应该控件的值,即使用事例代码中的注释部分。(去掉上面代码中的if段

  真正的解决方法,则规避不对ViewHolder中的元素进行赋值这种情况。拿上面的代码来说:

If (!t == null){
    mViewHolder.nameText.setText(t);
}


这个时候,在t == null 时,就没有对viewHolder进行赋值,所以在t == null时,界面上的元素就有可能是没有更新的,也就是重复上一个(这个位置视图)。所以可以加上一个else,并在里面对viewholder进行赋值。

 

If (!t == null){
    mViewHolder.nameText.setText(t);
}else{
    mViewHolder.nameText.setText("unknow");
}


这样问题就很好的解决了。

 

另外一种情况就是:

如果子控件为ImageView,当赋值内容为空时,直接设定默认的图像资源。如果使用setBackgroundResource可能出现默认的图像资源没有真正的设定到ImageView,必须使用setImageResource。setBackgroundResource是View类中的方法,而setImageResource是ImageView自己的方法。使用前者可能导致图像没有真正的设定到ImageView,此时ImageView显示上一次赋值的图像。

 

原文:http://www.cnblogs.com/3dant/archive/2011/04/06/2007060.html

 

你可能感兴趣的:(android开发总结)