Android ListView滑动过程中控件显示重复/错误问题之原理分析及解决方案

前言:

为了使ListView性能更优,最普遍的方法就是添加一个ViewHolder静态类。

虽然性能有很大的提高,但是同样也伴随着Item控件内容显示重复或错乱的情况。

分析并解决如下两个问题

一、控件数据未初始化而导致的显示错误。

二、网络异步加载导致出现显示错误、重复。

如下我们来简单分析一下ListView的缓存机制。我们整篇文章均以下图的模型来举例说明。

(图片转至http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html)

上图中的过程分析:

1.在getView()获取每行的Item并向下滑动的过程中,如果Item1已经完全滑出屏幕,且缓存中没有Item1对应的View,则将其put进缓存中。

2.将要滑入的Item8会先判断缓存中是否有可用的Item,如果有则直接将缓存中对应的View拿过来复用。

3.ListView显示刚刚滑入的Item8,并将ListView中的各个Item都执行刷新(getView())操作。

那么问题来了...

一、控件数据未初始化而导致的显示错误

执行Item8的getView()过程中由于复用的是Item1,那么Item8的所有初始值就是Item1的值,如果在Item8中没有对Item中任何控件重新赋值的话,那么显示的内容会和跟Item1一模一样。

实例代码:

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

        ViewHolder viewHolder;

        if (convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.listview,parent,false);
            viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }
	//Item7以上的所有Item都不赋值
        if(position <= 7){
            viewHolder.tvTitle.setText("title" + String.valueOf(position));
        }

        return convertView;
    }

运行效果如下

我们发现Item7之后由于未重新赋值,所以都是复用的ListView缓存机制中的View。

问题的解决:

这个问题比较好解决,就是把每项Item所有控件都赋值,这样就把缓存中的初始数据给覆盖掉。

代码如下

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

        ViewHolder viewHolder;

        if (convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.listview,parent,false);
            viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }

        //if(position <= 7){
            viewHolder.tvTitle.setText("title" + String.valueOf(position));
        //}

        return convertView;
    }

效果图:

问题解决。

二、网络异步加载导致出现显示错误、重复。

举个例子,比如Item有点赞功能,由于比较懒,未做实例,所以来来来...大家跟我一起在脑海中想象吧。

我们假设用户点了Item1的赞按钮(此时开始请求服务器...),当请求服务器的过程中,你突然向下滑动。

如上图所示,Item1被放入缓存,而Item8复用Item1的View。就在这时Item1的点赞请求刚刚请求成功,并执行显示赞状态的操作,此时Item8由于是复用的Item1的View,所以Item8的赞按钮就会被莫名其妙的点亮。然后就蒙圈了,就开始寻思,谁?谁啊?你谁?图片的加载错误也是这个原因。(图片的解决方案http://www.trinea.cn/android/android-listview-display-error-image-when-scroll/)

3个解决方案:

1.不使用 ViewHolder就不存在这种问题,每次有Item滑入会重新创建控件,但这你能用吗?好吧,愿意用就用吧;

2.HashMap来标记对应的View(HashMap<position,view>),如果item特别少你也可以用,如果Item多的话,有多少个View就会有多少个键值对,所以内存会越来越大, 也不科学;

3.Tag标记,最终方案。

下面我们来讲解一下通过Tag标记来解决上述问题。

先贴一下纯手打的代码

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

        ViewHolder viewHolder;

        if (convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.listview,parent,false);
            viewHolder.btnZ = (Button)convertView.findViewById(R.id.button);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }
	viewHolder.btnZ.setTag(position);
        viewHolder.btnZ.setText("踩");

        viewHolder.btnZ.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(//访问服务器成功){
                    if ((int) viewHolder.btnZ.getTag() == position) {
                        //异步显示 viewHolder.btnZ.setText("赞"); 的操作
                    }
                }
            }
        });
	return convertView;
    }

问题解决。

针对上面的代码,讲一下Tag在ListView中的所起的作用。

用户在Item1点了赞按钮后,此时Item1的Button进入请求服务器的过程中,用户向下滑,Item1滑出,将View复用给Item8并让其显示之后,此时Item1和Item8同时控制着同一个View,只不过Item8在显示的时候已经把复用的View重新按照它的数据覆盖了一下并把View上viewHolder.btnZ的tag重新标记成它的标记,而假设此时恰好Item1的请求才刚刚成功并要刷新了一下自己的控件,然而根据上面的代码...Item如果要显示自己的加载成功之后的状态,需要判断一下tag和position,此时Item1执行viewHolder.btnZ.getTag()方法,发现tag已经是7了(Item8的),而Item1自己的position是0。所以,被无情的拒绝在了门外~

所以我们用这种对比的方法就可以很轻松的来避免Item显示出错的问题。

总结一下:

在出现异步加载的过程中,滑动ListView,Item被复用且前一个Item刚刚异步加载成功的情况下。

getTag()获取的是当前正在显示的”正确的"Item标记,而position则很可能是复用View之前的那个Item。

如果了解上述原理,相信不管是图片还是button神马的都可以避免出现显示错误的问题啦。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 02:56:20

Android ListView滑动过程中控件显示重复/错误问题之原理分析及解决方案的相关文章

Android ListView滑动过程中图片显示重复错位闪烁问题解决[转载]

转自:here 主要分析Android ListView滚动过程中图片显示重复.错乱.闪烁的原因及解决方法,顺带提及ListView的缓存机制.1.原因分析ListView item缓存机制:为了使得性能更优,ListView会缓存行item(某行对应的View).ListView通过adapter的getView函数获得每行的item.滑动过程中, a. 如果某行item已经滑出屏幕,若该item不在缓存内,则put进缓存,否则更新缓存:b. 获取滑入屏幕的行item之前会先判断缓存中是否有可

Android ListView滑动过程中图片显示重复错乱闪烁问题解决

转自:http://www.oschina.net/question/221817_121051 主要分析Android ListView滚动过程中图片显示重复.错乱.闪烁的原因及解决方法,顺带提及ListView的缓存机制.1.原因分析ListView item缓存机制:为了使得性能更优,ListView会缓存行item(某行对应的View).ListView通过adapter的getView函数获得每行的item.滑动过程中, a. 如果某行item已经滑出屏幕,若该item不在缓存内,则p

android listview 滑动过程中不加载图片,停止时加载图片

今天闲来无事, 就测试了一下listview加载图片优化的功能, 在我们使用新浪微博的时候,细心的同学一定发现了,在滑动的过程中,图片是没有被加载的, 而是在滑动停止时,才加载图片了. 我们今天就做一个这样的效果吧. 我们先考虑两个问题: 1.在滑动停止的时候,如何获得需要加载的图片控件? 2.因为listiew在初始化完成的时候,OnScrollListener的onScrollStateChanged与onScroll并未被触发,如何初始化第一页的图片? package com.test.l

ScrollView 嵌套ListView 滑动冲突,与显示不全

import android.content.Context; import android.util.AttributeSet; import android.widget.ListView; /** * * @author jiarh *2014-8-14 */ public class UserListView extends ListView { public UserListView(Context context) { super(context); } public UserLis

Android - ListView 滑动载入下页数据 Scroll设定

Android - ListView 下滑载入新数据 遇到问题,过去的下滑载入功能很差,不知道原因. 试了半天终于试出来了. 觉得这个方法比较符合使用者体验的感觉, 不会感觉上视觉有跳动的感觉,而是很直直的载入,很顺. 以下是我设定的onScrollListener private OnScrollListener scrollListener = new OnScrollListener() { @Override public void onScroll(AbsListView view,

android listview异步加载图片错位,重复,闪烁分析以及解决方案

我们在使用listview异步加载图片 的时候,在快速滑动或者网络不好的情况下,会出现图片错位,重复,闪烁等问题,其实这些问题总结起来就是一个问题, 比如listview上有100个item,一屏只显示10个item,我们知道getView()中converView是用来复用view对象的,因为一个item的view对象,而imageview控件就是view通过findViewById()获得的,而我们在复用view对象时,也就是说这个imageview也被复用了,比如第11个item的view

Android ListView滑动删除及响应事件详解

目标:实现类似QQ,微信的消息列表滑动删除 具体操作: 1. 主页面布局 首先在布局文件(本例是activity_main.xml)中引入ListView控件,并指定id(如下代码中黑体部分). <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" andr

android listView 滑动载入数据 该数据是服务端获取的

package com.sunway.works.applycash; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import com.sunway.works.R; import com.sunway.works.applycash.ApplyCashListActivity; impo

android listView 的最后一个item显示(菜鸟新手,老鸟勿喷)

问题:一个listView显示不满全屏时,最后一个item会自动占满剩下的屏幕高度,而我们需要看到的是每个item布局所要占的高度一样. 解决办法:主要是把ListView布局里面的高度设置为:android:layout_height="fill_parent" : listView显示的每个item就占同样的高度了.