android中ListView异步加载图片时的图片错位问题解决方案-程序员宅基地

技术标签: 程序员  android  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

布局文件有两个,很简单,一个表示ListView(main.xml),一个表示ListView中的元素(single_data.xml),如下:

[java]  view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  3. xmlns:tools=“http://schemas.android.com/tools”

  4. android:layout_width=“fill_parent”

  5. android:layout_height=“fill_parent”

  6. android:orientation=“vertical”

  7. android:background=“@android:color/darker_gray”

  8. tools:context=“.MainActivity” >

  9. <ListView

  10. android:layout_width=“fill_parent”

  11. android:layout_height=“wrap_content”

  12. android:cacheColorHint=“@null”

  13. android:id=“@+id/listview”

  14. />

  15.   

[java]  view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  3. android:layout_width=“fill_parent”

  4. android:layout_height=“wrap_content”

  5. android:background=“@android:color/white”

  6. >

  7. <ImageView

  8. android:layout_width=“150dp”

  9. android:layout_height=“150dp”

  10. android:scaleType=“fitXY”

  11. android:id=“@+id/image_view”

  12. android:background=“@drawable/ic_launcher”

  13. />

  14. <TextView

  15. android:layout_width=“wrap_content”

  16. android:layout_height=“wrap_content”

  17. android:layout_alignTop=“@id/image_view”

  18. android:layout_alignBottom=“@id/image_view”

  19. android:layout_marginLeft=“20dp”

  20. android:layout_alignParentRight=“true”

  21. android:gravity=“center_vertical”

  22. android:layout_toRightOf=“@id/image_view”

  23. android:singleLine=“true”

  24. android:ellipsize=“end”

  25. android:text=“@string/hello”

  26. android:id=“@+id/text_view”

  27. />

  28.   

加入访问网络和读取,写入sdcard的权限。

[java]  view plain copy

  1.   
  2.   
  3.   

接下来,我们来看看MainActivity.java。性能考虑,我们使用convertView和ViewHolder来重用控件。这里涉及到比较关键的一步,我们会在getView的时候给ViewHolder中的ImageView设置tag,其值为要放置在该ImageView上的图片的url地址。这个tag很重要,在异步下载图片完成回调的方法中,我们使用findViewWithTag(String url)来找到ListView中对应的ImagView,然后给该ImageView设置图片即可。其他的就是设置adapter的一般操作了。

[java]  view plain copy

  1. public class MainActivity extends Activity {

  2. ListView mListView;

  3. ImageDownloader mDownloader;

  4. MyListAdapter myListAdapter;

  5. private static final String TAG = “MainActivity”;

  6. int m_flag = 0;

  7. private static final String[] URLS = {

  8. //图片地址就不贴了,自己去这篇帖子中找吧:http://www.cnblogs.com/liongname/articles/2345087.html

  9. //其中有几张图片访问不了。

  10. };

  11. @Override

  12. public void onCreate(Bundle savedInstanceState) {

  13. super.onCreate(savedInstanceState);

  14. setContentView(R.layout.main);

  15. Util.flag = 0;

  16. mListView = (ListView) findViewById(R.id.listview);

  17. myListAdapter = new MyListAdapter();

  18. mListView.setAdapter(myListAdapter);

  19. }

  20. private class MyListAdapter extends BaseAdapter {

  21. private ViewHolder mHolder;

  22. @Override

  23. public int getCount() {

  24. return URLS.length;

  25. }

  26. @Override

  27. public Object getItem(int position) {

  28. return URLS[position];

  29. }

  30. @Override

  31. public long getItemId(int position) {

  32. return position;

  33. }

  34. @Override

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

  36. //只有当convertView不存在的时候才去inflate子元素

  37. if (convertView == null) {

  38. convertView = getLayoutInflater().inflate(R.layout.single_data,

  39. null);

  40. mHolder = new ViewHolder();

  41. mHolder.mImageView = (ImageView) convertView.findViewById(R.id.image_view);

  42. mHolder.mTextView = (TextView) convertView.findViewById(R.id.text_view);

  43. convertView.setTag(mHolder);

  44. }else {

  45. mHolder = (ViewHolder) convertView.getTag();

  46. }

  47. final String url = URLS[position];

  48. mHolder.mTextView.setText(url != null ? url.substring(url.lastIndexOf(“/”) + 1) : “”);

  49. mHolder.mImageView.setTag(URLS[position]);

  50. if (mDownloader == null) {

  51. mDownloader = new ImageDownloader();

  52. }

  53. //这句代码的作用是为了解决convertView被重用的时候,图片预设的问题

  54. mHolder.mImageView.setImageResource(R.drawable.ic_launcher);

  55. if (mDownloader != null) {

  56. //异步下载图片

  57. mDownloader.imageDownload(url, mHolder.mImageView, “/yanbin”,MainActivity.this, new OnImageDownload() {

  58. @Override

  59. public void onDownloadSucc(Bitmap bitmap,

  60. String c_url,ImageView mimageView) {

  61. ImageView imageView = (ImageView) mListView.findViewWithTag(c_url);

  62. if (imageView != null) {

  63. imageView.setImageBitmap(bitmap);

  64. imageView.setTag(“”);

  65. }

  66. }

  67. });

  68. }

  69. return convertView;

  70. }

  71. /**

  72. * 使用ViewHolder来优化listview

  73. * @author yanbin

  74. *

  75. */

  76. private class ViewHolder {

  77. ImageView mImageView;

  78. TextView mTextView;

  79. }

  80. }

  81. }

上面的mDownloader.imageDownload()就是异步下载图片比较核心的方法,该方法在ImageDownloader.java类下。其中的五个参数分别为:要设置在当前ImageView 上的图片的url地址,当前ImageView,文件缓存地址,当前的activity以及图片回调接口。

在ImageDownloader类中,我们首先根据url从软引用中获取图片,如果不存在,从sdcard中读取图片,如果还不存在,则启动一个AsyncTask异步下载图片。注意注意:这里我们做了一个这样的操作:用一个map将当前的url及其对应的MyAsyncTask存放起来了。由于getView会执行至少一次,这一步的操作是为了相同的url创建相同的AsyncTask。在onPostExecute()方法中,将该url对应的信息从map中删除,一定要记得执行这一步。看到很多的异步图片下载的例子中,重复创建AsyncTask都是普遍存在的,这里我们使用上面的思路解决掉了这一问题。更详细的代码自己看ImageDownloader.java类吧,首先给出OnImageDownload.java接口的代码:

[java]  view plain copy

  1. public interface OnImageDownload {

  2. void onDownloadSucc(Bitmap bitmap,String c_url,ImageView imageView);

  3. }

ImageDownloader.java的代码(有两百多行,拷贝到eclipse中看会舒服一点):

[java]  view plain copy

  1. public class ImageDownloader {

  2. private static final String TAG = “ImageDownloader”;

  3. private HashMap<String, MyAsyncTask> map = new HashMap<String, MyAsyncTask>();

  4. private Map<String, SoftReference> imageCaches = new HashMap<String, SoftReference>();

  5. /**

  6. *

  7. * @param url 该mImageView对应的url

  8. * @param mImageView

  9. * @param path 文件存储路径

  10. * @param mActivity

  11. * @param download OnImageDownload回调接口,在onPostExecute()中被调用

  12. */

  13. public void imageDownload(String url,ImageView mImageView,String path,Activity mActivity,OnImageDownload download){

  14. SoftReference currBitmap = imageCaches.get(url);

  15. Bitmap softRefBitmap = null;

  16. if(currBitmap != null){

  17. softRefBitmap = currBitmap.get();

  18. }

  19. String imageName = “”;

  20. if(url != null){

  21. imageName = Util.getInstance().getImageName(url);

  22. }

  23. Bitmap bitmap = getBitmapFromFile(mActivity,imageName,path);

  24. //先从软引用中拿数据

  25. if(currBitmap != null && mImageView != null && softRefBitmap != null && url.equals(mImageView.getTag())){

  26. mImageView.setImageBitmap(softRefBitmap);

  27. }

  28. //软引用中没有,从文件中拿数据

  29. else if(bitmap != null && mImageView != null && url.equals(mImageView.getTag())){

  30. mImageView.setImageBitmap(bitmap);

  31. }

  32. //文件中也没有,此时根据mImageView的tag,即url去判断该url对应的task是否已经在执行,如果在执行,本次操作不创建新的线程,否则创建新的线程。

  33. else if(url != null && needCreateNewTask(mImageView)){

  34. MyAsyncTask task = new MyAsyncTask(url, mImageView, path,mActivity,download);

  35. if(mImageView != null){

  36. Log.i(TAG, "执行MyAsyncTask --> " + Util.flag);

  37. Util.flag ++;

  38. task.execute();

  39. //将对应的url对应的任务存起来

  40. map.put(url, task);

  41. }

  42. }

  43. }

  44. /**

  45. * 判断是否需要重新创建线程下载图片,如果需要,返回值为true。

  46. * @param url

  47. * @param mImageView

  48. * @return

  49. */

  50. private boolean needCreateNewTask(ImageView mImageView){

  51. boolean b = true;

  52. if(mImageView != null){

  53. String curr_task_url = (String)mImageView.getTag();

  54. if(isTasksContains(curr_task_url)){

  55. b = false;

  56. }

  57. }

  58. return b;

  59. }

  60. /**

  61. * 检查该url(最终反映的是当前的ImageView的tag,tag会根据position的不同而不同)对应的task是否存在

  62. * @param url

  63. * @return

  64. */

  65. private boolean isTasksContains(String url){

  66. boolean b = false;

  67. if(map != null && map.get(url) != null){

  68. b = true;

  69. }

  70. return b;

  71. }

  72. /**

  73. * 删除map中该url的信息,这一步很重要,不然MyAsyncTask的引用会“一直”存在于map中

  74. * @param url

  75. */

  76. private void removeTaskFormMap(String url){

  77. if(url != null && map != null && map.get(url) != null){

  78. map.remove(url);

  79. System.out.println(“当前map的大小==”+map.size());

  80. }

  81. }

  82. /**

  83. * 从文件中拿图片

  84. * @param mActivity

  85. * @param imageName 图片名字

  86. * @param path 图片路径

  87. * @return

  88. */

  89. private Bitmap getBitmapFromFile(Activity mActivity,String imageName,String path){

  90. Bitmap bitmap = null;

  91. if(imageName != null){

  92. File file = null;

  93. String real_path = “”;

  94. try {

  95. if(Util.getInstance().hasSDCard()){

  96. real_path = Util.getInstance().getExtPath() + (path != null && path.startsWith(“/”) ? path : “/” + path);

  97. }else{

  98. real_path = Util.getInstance().getPackagePath(mActivity) + (path != null && path.startsWith(“/”) ? path : “/” + path);

  99. }

  100. file = new File(real_path, imageName);

  101. if(file.exists())

  102. bitmap = BitmapFactory.decodeStream(new FileInputStream(file));

  103. } catch (Exception e) {

  104. e.printStackTrace();

  105. bitmap = null;

  106. }

  107. }

  108. return bitmap;

  109. }

  110. /**

  111. * 将下载好的图片存放到文件中

  112. * @param path 图片路径

  113. * @param mActivity

  114. * @param imageName 图片名字

  115. * @param bitmap 图片

  116. * @return

  117. */

  118. private boolean setBitmapToFile(String path,Activity mActivity,String imageName,Bitmap bitmap){

  119. File file = null;

  120. String real_path = “”;

  121. try {

  122. if(Util.getInstance().hasSDCard()){

  123. real_path = Util.getInstance().getExtPath() + (path != null && path.startsWith(“/”) ? path : “/” + path);

  124. }else{

  125. real_path = Util.getInstance().getPackagePath(mActivity) + (path != null && path.startsWith(“/”) ? path : “/” + path);

  126. }

  127. file = new File(real_path, imageName);

  128. if(!file.exists()){

  129. File file2 = new File(real_path + “/”);

  130. file2.mkdirs();

  131. }

  132. file.createNewFile();

  133. FileOutputStream fos = null;

  134. if(Util.getInstance().hasSDCard()){

  135. fos = new FileOutputStream(file);

  136. }else{

  137. fos = mActivity.openFileOutput(imageName, Context.MODE_PRIVATE);

  138. }

  139. if (imageName != null && (imageName.contains(“.png”) || imageName.contains(“.PNG”))){

  140. bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);  
    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

架构篇

《Jetpack全家桶打造全新Google标准架构模式》

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。[外链图片转存中…(img-vuSwpGWQ-1712544837433)]

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-lJEx95pu-1712544837433)]

架构篇

《Jetpack全家桶打造全新Google标准架构模式》
[外链图片转存中…(img-HWYeW9Gt-1712544837434)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

智能推荐

iMeta | 宁波大学附属第一医院崔翰斌团队综述缺血性心脏病相关肠道微生物及菌群代谢物研究进展...-程序员宅基地

文章浏览阅读551次。点击蓝字 关注我们缺血性心脏病相关肠道微生物及菌群代谢物研究进展iMeta主页:http://www.imeta.science综 述●原文链接DOI: https://doi.org/10.1002/imt2.94● 2023年2月26日,宁波大学附属第一医院崔翰斌团队、浙江省动脉粥样硬化疾病精准医学研究重点实验室范勇团队在iMeta在线发表了题为“Microbiota-related ..._与急性心肌梗死有关的微生物

图形图形处理方面的一位微软专家的主页,_automated video looping with progressive dynamism-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏5次。刚在Github上分享了一些不错的代码http://hhoppe.com/ Hugues Hoppe »DemosPublicationsTalksAcademicProfessionalMisc Hugues Hoppe [pronunciation]Principal researche_automated video looping with progressive dynamism

AOP实现权限拦截_apo拦截控制层-程序员宅基地

文章浏览阅读497次。AOP实现权限拦截注解名称:CheckUnSysAdmin注解实现类:CommonAspectController层方法上引入注解名称:CheckUnSysAdminpackage com.sf.XWFS.aop;import java.lang.annotation.*;/** * @author cc * Desc 校验除超管外的角色,都进行拦截 */@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType_apo拦截控制层

杀毒软件业野蛮生长法则:自己研发病毒自己杀-程序员宅基地

文章浏览阅读52次。时隔4个月后,瑞星杀毒造假案又有了戏剧性的变化。近日,瑞星杀毒造假案的主角——北京市公安局网监处原处长于兵的二审结果仍维持一审的死缓判决。而据于兵的最新供认资料,相当一部分病毒是杀毒软件公司自己的科技力量研制的。于兵供认,瑞星公司向其行贿时就提出条件,由公安机关发出病毒警报,提示用户下载该公司杀毒软件进行杀毒,而病毒则是由瑞星公司“研制”的。“其实这是杀毒软件行业里的公开秘密。”国内一家知名...

密码学考点整理_移位密码和vigenere密码的异同是什么-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏35次。考试重点1. 密码体制分类对称密码体制和非对称密码体制;2. DES和AES算法的特点(结构、密钥长度,分组长度,DES弱密钥)及其过程(置换过程,S盒查表过程),AES的轮结构DESDES结构首先是一个初始置换IP,用于重排明文分组的64比特;相同功能的16轮变换,每轮都有置换和代换;第16轮的输出分为左右两半并被交换次序;最后经过一个逆初始置换产生64比特密文;DES结构图如下:密钥长度:56分组长度:64DES弱密钥:待续了解即可DES 分组长度_移位密码和vigenere密码的异同是什么

基于微信小程序+Springboot线上租房平台设计和实现【三端实现小程序+WEB响应式用户前端+后端管理】_微信小程序租房平台怎么弄-程序员宅基地

文章浏览阅读2.7w次,点赞97次,收藏158次。系统功能包括管理员服务端:首页、轮播图管理、公告信息管理、系统用户(管理员、租客用户、房主用户)资源管理(新闻列表、新闻分类列表)模块管理(房源信息、房源咨询、租赁申请、入住信息、房租信息、反馈信息、通知信息、房屋类型)个人管理;用户客户端:首页、公告信息、新闻资讯、房源信息等功能。_微信小程序租房平台怎么弄

随便推点

微信公众号网页静默授权/非静默授权(uniapp版)_微信公众号静默授权-程序员宅基地

文章浏览阅读7.7k次,点赞5次,收藏33次。一、问题为什么要进行网页授权?首先我们进行网页授权的需求是,获取用户信息、最主要是获取openid唯一值,可以用于用户登录、支付等功能,这时候就需要进行网页授权获取用户的信息以及openid。二、静默授权/非静默授权在操作之前可以先提前看看网页授权官方文档静默授权snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid;用来获取进入页面的用户的openid的,并且自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)。非静默授权snsapi_user_微信公众号静默授权

A Key Volume Mining Deep Framework for Action Recognition-程序员宅基地

文章浏览阅读235次。A Key Volume Mining Deep Framework for Action Recognition_a key volume mining deep framework for action recognition

python创建窗体_python生成窗口-程序员宅基地

文章浏览阅读3.9k次。广告关闭腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!2、python生成目录树上述 cmd 方式虽然可以生成目录树,但是并不美观,让我们用 python 实现。 2.1 标准库pathlib介绍python有一个标准文件路径处理库 os.path ,从 python3.4 开始,python 又加入了一个标准库 pathlib ,该库..._python创建一个窗口

PowerDesigner16 时序图_使用powerdesiger 画出时序图有接口 控制-程序员宅基地

文章浏览阅读5.1k次,点赞5次,收藏10次。时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。顺序图中显示的是参与交互的对象及其对象之间消息交互的顺序。时序图中包括的建模元素主要有:角色(Actor)、对象(Object)、生命线(Lifeline)、控制焦点(Focus of control)/ 激活(Activation)、消息(Message)、组合片段(Combined Fragments_使用powerdesiger 画出时序图有接口 控制

Doris系列17-动态分区_dynamic_partition.history_partition_num-程序员宅基地

文章浏览阅读1.2k次。文章目录一. 动态分区概述1.1 原理1.2 使用方式1.3 动态分区规则参数1.4 创建历史分区规则1.5 注意事项二. 案例2.1 案例12.2 案例22.3 案例3参考:一. 动态分区概述动态分区是在 Doris 0.12 版本中引入的新功能。旨在对表级别的分区实现生命周期管理(TTL),减少用户的使用负担。目前实现了动态添加分区及动态删除分区的功能。动态分区只支持 Range 分区。名词解释:FE:Frontend,Doris 的前端节点。负责元数据管理和请求接入。BE:Backend_dynamic_partition.history_partition_num

Linux命令_禅道的运行日志放在哪-程序员宅基地

文章浏览阅读309次。笔记_禅道的运行日志放在哪

推荐文章

热门文章

相关标签