Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案-程序员宅基地

技术标签: java  json  移动开发  

我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位、重复、闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化。

比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而ImageView控件就是View对象通过findViewById()获得的,而我们在复用View对象时,同时这个ImageView对象也被复用了。比如第11个Item的View复用了第1个Item View对象,那么ImageView就同时被复用了,所以当图片没下载出来,这个ImageView(第11个Item)显示的数据就是复用(第1个Item)的数据。

 

1:Item图片显示重复

这个显示重复是指当前行Item显示了之前某行Item的图片。

比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中ListView已经滑动到了第14行,且滑动过程中该图片加载结束。第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的View对象可能被第14行复用,这样我们看到的就是第14行显示了本该属于第2行的图片,造成显示重复。

2. Item图片显示错乱

这个显示错乱是指某行Item显示了不属于该行Item的图片。

跟上面的原因一样。

3. Item图片显示闪烁

上面介绍的另外一种情况,如果第14行图片又很快加载结束,所以我们看到第14行先显示了复用的第2行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。

解决方案:

通过上面的分析我们知道了出现错乱的原因是异步加载及对象被复用造成的,如果每次getView能给对象一个标识,在异步加载完成时比较标识与当前行Item的标识是否一致,一致则显示,否则不做处理即可。

 

下面给个简单的实例(此实例没有对图片做缓存!!!!!!)

 

MainActivity --------------------------------------------------------------------------------------------------

package com.example.day001;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;
/**
* @author echo
*
* ListView的使用步骤:
* 1.在xml中使用ListView标签,并找到该listview。
* 2.创建一个数据源(ListView要展示的数据)
* 3.自定义一个BaseAdapter并实例化它。
* 4.lv.setAdapter(adapter);
*
*/
public class MainActivity extends Activity {

// 集合,用来放数据源
List<PeopleBean> list;
// 自定义的BaseAdpater
MyBaseAdapter adapter;
// 当前的页数
int page = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 1.找到listview
ListView lv = (ListView) findViewById(R.id.lv);

// 2.创建一个数据源
// 用来装数据的集合
list = new ArrayList<PeopleBean>();
// 联网请求数据
MyTask myTask = new MyTask();
myTask.execute("http://www.ytmfdw.com/coupon/index.php?c=user&a=getstudent&page="
+ page);

// 3.实例化adapter
adapter = new MyBaseAdapter(list, this);

// 4.设置adapter
lv.setAdapter(adapter);

// 设置listview的滚动监听事件
lv.setOnScrollListener(new OnScrollListener() {

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == 0) {
// 当listview滑动到底部的时候,需要联网加载更多数据
if (view.getLastVisiblePosition() == view.getCount() - 1) {
page++;
MyTask task = new MyTask();
task.execute("http://www.ytmfdw.com/coupon/index.php?c=user&a=getstudent&page="
+ page);
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});

}


/**
* 联网获取接口返回的数据
*
* 异步任务的使用步骤:
* 1.新建类,继承自AsyncTask<Params, Progress, Result>
*          三个参数:(1)Params:代表输入到任务的参数类型,也即是doInBackground()的参数类型


*                        (2)Progress:代表处理过程中的参数类型,也就是doInBackground()执行过程中的
*                                             产出参数类型,通过publishProgress()发消息,传递给
*                                             onProgressUpdate(),一般用来更新界面.


*                        (3)Result:任务结束的产出类型,也就是doInBackground()的返回值类型,
*                                          和onPostExecute()的参数类型


*           这三个参数你需要什么类型的就传入什么类型的,如果是图片,可以传入Bitmap
*
* 2.重写doInBackground()方法,在这个方法中进行联网请求数据
*           它返回的值会传给onPostExecute()。
*
* 3.重写onPostExecute()方法,在这个方法中进行UI的更新
*
* 4.重写onProgressUpdate()方法,这个方法可以用于更新进度,比如下载电影,下载到百分之几。
* 它的进度值则是通过publishProgress()这个方法发布出来的。


*/


class MyTask extends AsyncTask<String, String, String> {
/**
* 此方法是在子线程中
* ------注意:联网请求只能写在子线程中
*/
@Override
protected String doInBackground(String... params) {
/**
* String... params:泛型
*        如果调用异步任务类的时候---->
*             MyTask myTask = new MyTask();
               myTask.execute(url1,url2,……);
    execute里面的参数有多个值,这些值会被放在params里面,成为一个数组,
    取值的时候就像数组一样取值,想要第几个,就用params[index]取出来就行。
    如果只有一个参数,则直接用params[0]即可。
*/
String http = params[0];
HttpURLConnection conn = null;
StringBuilder sb = new StringBuilder();
try {
URL url = new URL(http);
conn = (HttpURLConnection) url.openConnection();
InputStream in = conn.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
String tmp = null;
while ((tmp = reader.readLine()) != null) {
sb.append(tmp);
}
in.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
Log.d("TAG", sb.toString());

return sb.toString();
}

/**
* 此方法是在主线程中。
* ----注意:UI更新只能在主线程中进行,不能在子线程中更新UI
*/
@Override
protected void onPostExecute(String result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
try {
// json解析
JSONObject json = new JSONObject(result);
JSONArray jsons = json.getJSONArray("data");
int len = jsons.length();
for (int i = 0; i < len; i++) {
JSONObject obj = jsons.getJSONObject(i);
PeopleBean bean = new PeopleBean();
bean.img = obj.getString("image");
bean.name = obj.getString("name");
list.add(bean);
}

// 刷新适配器,当数据改变的时候调用该方法,listview显示的数据就改变
adapter.notifyDataSetChanged();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

activity_main.xml----------------------------------------------------------------------------------------------------

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.day001.MainActivity" >

<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</RelativeLayout>

 

PeopleBean---------------------------------------------------------------------------------

public class PeopleBean {
String img;
String name;
String sex;
}

 

MyBaseAdapter---------------------------------------------------------------------------------

package com.example.day001;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
/**
* @author echo
*
* 一、自定义BaseAdapter步骤:
* 1.新建一个类,继承BaseAdapter
* 2.重写4个方法;
* 3.修改getCount()返回的值(listview要展示的数据总个数)
* 4.在getView()方法里面去展示数据。
*        ---convertView复用:内存空间优化
*        ---ViewHolder:运行时间优化
*
* 二、ListView异步加载图片错位、重复、闪烁
*
解决办法:为了防止ListView异步加载图片错位、重复、闪烁等情况,我们需要给ImageView设置一个Tag,
这个Tag中设置的是图片的url,在异步加载完成时我们可以比较下图片的url与当前行Item的标识是否一致,
一致则显示,否则不做处理即可。

解决步骤:
1.给当前ImageView对象设置标识tag
2.给当前ImageView设置一个默认的图片
3.联网下载到图片后判断下tag值和当前的图片地址是否相等,相等就展示出来。

*
*/
public class MyBaseAdapter extends BaseAdapter {

List<PeopleBean> data;
Context context;

// 有参构造方法,传递值过来
public MyBaseAdapter(List<PeopleBean> data, Context context) {
super();
this.data = data;
this.context = context;
}

// 返回数据长度
@Override
public int getCount() {
// TODO Auto-generated method stub
return data.size();
}

// 返回一个item的view
@Override
public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

/**
* ListView中的每一个Item显示都需要Adapter调用一次getView的方法, 这个方法会传入一个convertView的参数,
* 返回的View就是这个Item显示的View
* 。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象
* (mInflater.inflate(R.layout.lv_item,
* null);从xml中生成View,这是属于IO操作)也是耗时操作
* ,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器
* )的构件,就是当ListView的Item从上方滚出屏幕视角之外
* ,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item
* ,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View
* ,所以说如果能重用这个convertView,就会大大改善性能。
*/
if (convertView == null) {

// 1.布局容器
LayoutInflater inflater = LayoutInflater.from(context);
// 2.把布局文件装进布局容器里面,得到item的view
convertView = inflater.inflate(R.layout.items, null);
holder = new ViewHolder();
// holder设置tag(标记)值
convertView.setTag(holder);
} else {
/**
* 如果convertView不为空,说明该view之前加载进来过,所以直接将其赋
* 给view,即反复使用,避免再创建新的view浪费资源
*/
holder = (ViewHolder) convertView.getTag();
}

// 找到当前的对象
PeopleBean PeopleBean = data.get(position);
// 获取当前对象里面的图片
String img_url = PeopleBean.img;
// 获取当前对象里面的文字
String text = PeopleBean.name;

// 找到控件
holder.tv = (TextView) convertView.findViewById(R.id.tv);
holder.iv = (ImageView) convertView.findViewById(R.id.iv);

/**
* 为了防止ListView异步加载图片错位、重复、闪烁等情况,我们需要给ImageView设置一个Tag,
* 这个Tag中设置的是图片的url,在异步加载完成时我们可以比较下图片的url与当前行Item的标识是否一致,
* 一致则显示,否则不做处理即可。
*/

/**
* 防止ListView异步加载图片错位、重复、闪烁等情况
*
* ---1.给当前ImageView对象设置标识
*/
holder.iv.setTag(img_url);
/**
* 防止ListView异步加载图片错位、重复、闪烁等情况
*
* ---2.给当前ImageView设置一个默认的值(因为控件是复用的, 用完之后其中的值还存在,所以需要清洗下,
* 这里可以给随意给一张默认的图片)
*/
holder.iv.setImageResource(R.drawable.ic_launcher);

// 异步任务--下载图片
DownImgTask downImgTask = new DownImgTask(holder.iv);
downImgTask.execute(img_url);

// 给控件设置值
holder.tv.setText(text);

return convertView;
}

/**
* 联网下载图片
* @author echo
*
*/
class DownImgTask extends AsyncTask<String, Void, Bitmap> {

ImageView iv;
String img_url;// 图片的地址

public DownImgTask(ImageView iv) {
super();
this.iv = iv;
}

@Override
protected Bitmap doInBackground(String... params) {
img_url = params[0];
HttpURLConnection conn = null;
Bitmap bm = null;
try {
URL url = new URL(img_url);
conn = (HttpURLConnection) url.openConnection();
InputStream in = conn.getInputStream();
bm = BitmapFactory.decodeStream(in);
in.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return bm;
}

@Override
protected void onPostExecute(Bitmap result) {
// TODO Auto-generated method stub
super.onPostExecute(result);

/**
* 防止ListView异步加载图片错位、重复、闪烁等情况
*
* ---3.判断下tag值和图片地址是否相等,若相等,则说明该控件在当前的位置所需要展示的图片 是对的,就可以直接展示出来啦
*/

// 防止图片错位,判断下tag值和图片地址是否相等
if (result != null && img_url.equals(iv.getTag())) {
// 设置图片
iv.setImageBitmap(result);
}

}
}

// 自定义一个ViewHolder
class ViewHolder {
TextView tv;
ImageView iv;
}

@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}

}

 

转载于:https://www.cnblogs.com/jingmo0319/p/5633037.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30872671/article/details/96926769

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签