提示:点击↑上方蓝字"杨守乐"关注我! 来源:csdn 作者:奋斗之路 链接:http://blog.csdn.net/dmk877/article/details/49366421(点击尾部阅读原文前往) ListView这个控件对于大家肯定不会陌生,即使你是初学者相信也会用ListView。因为ListView这个控件实在是太常用,可以说基本上每一个项目开发都会用到它,今天这篇博客主要讲解,ListView异步加载图片的问题,相信通过本篇博客的学习你将有意想不到的收获。如有谬误请批评指正,如有疑问请留言。 1.内容介绍 通过本篇博客你将学到 ①ListView异步加载图片的方式 ②给ImageView设置Tag,解决图片覆盖问题 ③采用LruCache缓存已经加载过的图片 ④当ListView滚动时不加载图片,滚动停止时才加载图片,从而达到ListView滑动很流畅的效果 ⑤当ListView加载图片时只加载当前屏幕内可见的条目 上述5种功能是我们经常在项目中遇到的,这也是用户体验最好的方式,那么今天就和大家一步一步探讨上述5种方式的实现方式。光说没用首先我们来上张图,看看我们究竟实现了什么效果 从上面图片我们可以清楚的看到,当我们滑动ListView时是不加载图片的,当停止时才异步加载图片,而且只加载当前可见条目的图片,并且进行了缓存,当下次加载时直接从缓存读取图片,这就是我们要达到的整体的效果,进入正题。 2.ListView异步加载图片的方式 (1)采用多线程的方式Handler Message异步加载图片 首先我们来看第一种方式采用多线程,首先我们的方法需要两个参数,①显示图片的控件,②图片的路径 因为在子线程中是不能对UI进行更新的,所以我们需要通过Handler Message的方式去在主线程中去更新UI,因此我们还需要一个Handler对象 这样我们将耗时的操作放在子线程中待其拿到数据后发一条消息到主线程中,从而在主线程中进行更新显示。 在showImageByThread方法中用到了getBitmapFromUrl方法,它的代码如下 (2)采用Android中AsyncTask 这种方式比较简单,如果你不熟悉AsyncTask的用法,请先学习下。这种方法其实就是对上面方法的封装我们创建一个方法showImageByAsyncTask,这个方法同样需要两个参数一个是显示图片的ImageView一个是图片的路径Url
以上就是异步加载图片的两种常用的方式。 接下来我们上个实例,试试上面两个方法行不行啊,这里我们只将Adapter的getView方法方法贴出来我们可以
认真看上图,发现实现了图片的异步加载,但是有个问题,请仔细看图片是不是有图片被重复替换即:先显示一张图片然后又变成了另一张图片,看鼠标晃动的位置你会发现有的图片显示好了之后又被替换掉了,有的还不止被替换一次,这是为什么呢?这个问题就要追究到ListView的复用的问题上了,我们想一下当ListView中有100个Item时,它怎么加载?肯定不会傻到一次把100条全部加载完吧。其实它只是将当前可见的条目加载出来。这个思想跟手游有点像,有的手机游戏:控制一个小人不停的往前跑,画面是不断的变化的,其实它也是只加载当前可见的画面,并不是一次全部加载出来的。但是ListView又跟上面这个例子有很大的区别,因为它还有复用这一说,我们看看getView 注:上图是我参考网上的一张图片画的,为什么不用原图,因为我觉得原图是有问题的,首先我们一共有7个Item当滑动时第8个Item刚漏出时,此时Item的缓存池中是没有Item的,因此这一个Item也是重新创建的,所以一共会创建8个item,当第9个Item过来的时候它会去Recyler的缓存池中去获取发现有就会复用。所以复用是在产生第9个Item开始的。 了解了Adapter的convertView复用之后我们来分析一下为什么会产生图片替换的现象。 由于网络请求是一个比较耗时的操作,当我们快速滑动ListView的时候有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始开始请求网络图片,但是还没等图片下载完成,它就被移出了屏幕,这样会导致什么结果呢?因为被移出屏幕的Item将会很快被进入屏幕的元素重新利用,而如果这个时候被复用的这个Item的图片请求还没有完成(这个请求是在子线程的),也就是说被复用的Item中的图片的网络请求还没有完成就被复用了,当被复用的Item中的网络图片请求完成时就会显示在Item的ImageView上,而新进入的Item也同样会发出一条图片的网络请求但它比被复用的Item晚那么一点点,当它请求完成时同样也会显示在Item的ImageView上,由于这个Item是复用的上面的Item所以它将把复用的Item的数据清除然后显示自己的网络请求图片,所以我们会看到上图中ImageView先显示一张图片接着就变为另一张图片的这种现象。 知道了这种情况原因那么我们怎么解决这个问题呢? 其实解决方案有多种,这里我们采用给ImageView设置Tag的方式,将ImageView的url设置为它的Tag,这样在每次显示图片的时候判断ImageView的tag是不是我们设置的如果是就显示图片。这样Adapter中的getView方法的代码如下 这样我们在显示图片的时候就要做一个判断,判断当前的ImageView的tag是否和我们所设置的相对应,于是乎在我们的ImageLoader类上面两种加载图片的方法中应该修改如下 采用多线程的方式Handler修改如下: 增加了一个mImageView.getTag().equals(mUrl)判断。 同理采用AsyncTask方式修改如下
是吧没有出现我们上面的先显示一张图片又被替换掉的那种现象。这样①和②就解决了。但是和文章最开始的那张图还有差别的,仔细看你会发现,当ListView显示了一屏后,此时滑动ListView再滑动回来图片又加载了一次,这样的用户体验太差,可能我滑过去之后想回来看看之前的条目,但是你又给我加载了一遍,一方面浪费了流量,另一方面体验也不好,那么我们怎么办呢?解决这个问题的办法是采用LRU cache。 3.采用LruCache缓存已经加载过的图片 看到Lru你是否感觉熟悉?LRU是(Least Recently Used缩写)即近期最少使用算法,在操作系统中它是一种页面置换算法,这里就不多扯了,大家知道就行了,其实现逻辑谷歌工程师都已经帮我们写好了,我们只需要拿过来用就可以了,它的主要原理就是把最近使用的对象存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设的值之前从内存中移除。那么它怎么使用呢?先看一个例子
创建好LruCache后,要想实现缓存我们需要在我们上篇博客中创建的ImageLoader中创建两个方法getBitmapFromLrucache(String url)和addBitmapToLrucaches(String url,Bitmap bitmap)这两个方法的实现非常的简单,因为LruCache有点类似于Map,通过Key,Value的方式进行存储,所以我们操作起来很方便,注意这里我们不需要释放内存的方法Lru算法可以保证cacheSize不会OOM,一旦超过这个大小,GC就会回收时间最长的对象,释放空间。 getBitmapFromLrucache(String url)的代码如下
我再擦,就两行代码,没错就两行代码,那么我们怎么将其运用到项目中去呢?其实也很简单,它的思路是这样的,每次在显示图片的之前我首先去Lru Cache中去根据Key去找图片,如果有的话就拿过来直接显示,如果没有的话,我就开启一个异步任务去下载,下载完成后将此Bitmap加入到Lru Cache中,这样就能保证图片最多只加载一次。它的思路如下图 图片 那么具体到代码是这样的,这里以showByAsyncTask方法为例
以上就解决了缓存加载过的图片的功能,但是还有个问题,请仔细看图片,我故意让ListView上下不停的滑动了一会,发现在滑动的过程中它也在加载图片,另外当我很快滑到底部的时候你会发现屏幕的最上面一个Item过了一小会才加载进来,这样的用户体验是不好的,因为最好的用户体验是我在滑动的时候停止所有的加载,当我停止的时候只加载我当前可见的Item,而上面不是这样的,它是从上往下加载所以我一下滑到底部时它会等一会才加载到我当前可见的Item,那么问题来了,我们怎么解决以上问题呢?即滑动不加载,停止时加载,且只加载当前可见条目。 4.滑动不加载,停止加载并且只加载当前可见条目 这种功能应该怎么实现呢?这里我们只需要对当前的ListView设置滚动监听就行了,我们都知道ListView有个OnScrollListener,有两个方法
①firstVisibleItem:它表示当前显示在手机屏幕上的第一个Item的位置 ②visibleItemCount:它表示当前显示在手机屏幕上的可见条目的个数 ③totalItemCount:它表示ListView条目的总数 所以firstVisibleItem visibleItemCount的和就是当前ListView显示在屏幕上的条目的位置范围,举个简单的例子,比方说ListView有100条数据,在滑动ListView停止后ListView的第20个条目显示在了手机屏幕的最上面即firstVisibleItem=19(firstVisible从0开始),而我们手机一屏可以显示10个Item那么,我们可以推算出当前显示在手机屏幕上的Item是ListView中的第20~30的Item。鉴于以上分析我们的代码要改了因为和刚才实现的逻辑不一样了,这次我们要实现的加载从firstVisibleItem到firstVisibleItem visibleItemCount的Item,所以在这里我们要在我们创建的ImageLoader中重新创建一个方法接收两个参数start和end代码如下
第一:mUrls是啥?因为你我们要加载从start到end的数据,所以刚才的那个传递ImageView和其url的方法肯定不能用了,所以针对url我们在ImageLoader类中创建一个变量,在Adapter中new ImageLoader的时候将所有的图片的路径存储到此数组,所以Adapter中会有如下代码
第二:因为我们这次是显示从start到end的数据,ImageView是哪一个啊?怎么和url对应?从上面Adapter的构造方法中可以看到我们将ListView传递给我ImageLoader,因为我们的ImageView是设置了Tag的,所以我们可以通过ImageView
imageView = (ImageView) mListView.findViewWithTag(loadUrl);来找到要显示图片的ImageView,这样ImageView的问题就解决了, |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|