安卓ListView自定义Adapter实时加载网络图片与缓存软引用图片之说明
这下问题出来了.因为是获取网络图片.按传统的做法没办法及时加载对应的图片或者图片错位.
在网上找了很久.也抠了一天的源码..发现网上的都没有比较系统的说明.所以这里整理一下.
方便以后自己回看.
========================
先说一下思路: 我的理解为---- 因为要网络操作.所以加载图片在子线程中. 有延迟.但主线程都不等你子线程是否获取结果.它就走下去了.这样setImageDrawable(这里当然是没有了).
所以就会在你到ListView加载完时.看不到图片的原因.
那么.在加载图片的子线程中,如果获取到图片之后.就handler发送一个信息到主线程.让它根据当前行的下标(postion)来更新图片.我管你主线程跟到哪了.管你等不等我.
反正我慢慢地下载图..下载到了我再叫你更新.
========================
首先说自定义的SimpleAdapter..
这里的传统做法大家都应懂的了.就是那个getView() 方法可以有点难理解
简单地说. 就是加载每一行数据(单行ListView).就调一次getView()
public View getView(int position, View convertView, ViewGroup parent){}
position: 这个参数是指当前一行的下标. (从0开始的);
converTiew: 是可以理解为当前一屏..(不知对不对.我是这样理解的.)第一次执行convertView,如果是第一次就进行布局资源的创建操作
如果拖进屏幕时.就可以复用到它了.不用每一屏都新建一个.这里下面代码里有说明
到图片加载了.我们定义一个图片加载的类.用一个静态方法来获取图片的Drawable
但由于优化内存使用,为了ListView加载了太多图片在内存中.那么.我们就进行缓存软引用机制来管理图片.
说得这么绕..无非就是指, 把得到的Drawable变成一个软引用.然后再把它放进map中.让系统自己的决定什么时候回收内存中的图片.
关于软引用...我个人的用法就是.但到一个drawable之后.马上new SoftReference<Drawable> (drawable) 存到map 中...那什么时候变回普通drawable呢
我认为当要从map中取出来之后.第一步就要变回普通的drawable(--softReference.get()--).这样的话.当我回来拖进listView时...就不会因为系统清理我的软引用导致看不到图了
下面上代码..先上异步获取图片的类
AsyncImageTask.java
package com.naxieshu.util; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.util.HashMap; import java.util.Map; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; /** * 异步加截图片类 * @author Hai Jiang * @Email [email protected] * @Data 2014-2-26 */ public class AsyncImageTask { //缓存图片 把图片的软引用放到map中 private Map<String, SoftReference<Drawable>> imageMap; //构造器 public AsyncImageTask() { super(); this.imageMap = new HashMap<String, SoftReference<Drawable>>(); } //ID为标记,标记哪条记录image . 这个ID来自于自定义adapter的getView()方法中其中一个参数position public Drawable loadImage(final int id, final String imageUrl, final ImageCallback callback){ //先看缓存(Map)中是否存在 if(imageMap.containsKey(imageUrl)){ SoftReference<Drawable> softReference = imageMap.get(imageUrl); Drawable drawable = softReference.get(); if(drawable != null){ return drawable; } } //主线程更新图片 final Handler handler = new Handler() { public void handleMessage(Message message) { callback.imageLoaded((Drawable) message.obj, id); } }; //加载图片的线程 new Thread() { public void run() { //加载图片 Drawable drawable = AsyncImageTask.loadImageByUrl(imageUrl); //加入缓存集合中 注意 这里就要把得到的图片变成软引用放到map中了 imageMap.put(imageUrl, new SoftReference<Drawable>(drawable)); //通知消息主线程更新UI . 这里就是是否能异步刷新的留意点. Message message = handler.obtainMessage(0, drawable); handler.sendMessage(message); } }.start(); return null; //到这里就获取图片的静态方法就完了 } //根据图片地址加载图片,并保存为Drawable //这里不用说了吧.都是一些基本的.从API从可以看 public static Drawable loadImageByUrl(String imageUrl){ URL url = null; InputStream inputStream = null; try { url = new URL(Constant.TARGETURL+imageUrl); inputStream = (InputStream) url.getContent(); Drawable drawable = Drawable.createFromStream(inputStream,"src"); return drawable; } catch (Exception e) { e.printStackTrace(); } finally { try { if(inputStream != null) inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } //利用接口回调,更新图片UI public interface ImageCallback { public void imageLoaded(Drawable obj, int id); } }
这里是自定义adapter类
MyListAdapter.java
package com.naxieshu.adapter; import java.lang.ref.SoftReference; import java.util.List; import java.util.Map; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; import com.naxieshu.activity.FindActivity; import com.naxieshu.activity.R; import com.naxieshu.domain.Book; import com.naxieshu.util.AsyncImageTask; import com.naxieshu.util.AsyncImageTask.ImageCallback; import com.naxieshu.util.ImageUtil; /** * 自定义List内容控件 * @author Hai Jiang * @Email [email protected] * @Data 2014-2-26 */ public class MyListAdapter extends SimpleAdapter{ //要显示在LISTVIEW中的数据源 public List<? extends Map<String, ?>> data; private LayoutInflater inflater; private AsyncImageTask imageTask; private ListView listView; public MyListAdapter(ListView listView,Context context, List<? extends Map<String, ?>> data) { super(context, data, 0, null, null); this.data = data; this.listView = listView; inflater = LayoutInflater.from(context); imageTask = new AsyncImageTask(); } /** * 在创建View资源对象的时候提供效率的缓存策略 */ class ViewHold{ //book.cover public ImageView image; //book.title book.shortIntro public TextView namtView,idView,introView; } ViewHold hold =null; @Override public View getView(int position, View convertView, ViewGroup parent) { //判断是否第一次执行convertView(显示屏),如果是第一次就进行布局资源的创建操作 if (convertView == null){ hold = new ViewHold(); //填充加载布局资源 convertView = inflater.inflate(R.layout.activity_find_listview, null); hold.image = (ImageView)convertView.findViewById(R.id.bookImage); hold.namtView = (TextView)convertView.findViewById(R.id.bookName); hold.idView = (TextView)convertView.findViewById(R.id.bookId); hold.introView = (TextView)convertView.findViewById(R.id.bookShortIntro); //保存标记 convertView.setTag(hold); } else { hold = (ViewHold) convertView.getTag(); } /**获取数据,进行数据填充*/ // 标记图片视图,注意不能放在上面 hold.image.setTag(position); Book book = (Book) data.get(position).get(position+""); //异步获取图片.注意: 这里就是上面说到的.我个人为了方便理解的: 它里面子线程获取图片时可能会有延迟. // 主线程是不会等你这里的drawble得到值再继续往下走的.下面的判断是否获取网络图片就先给一个本地图它用着 Drawable drawable = imageTask.loadImage(position, book.getCover(), new ImageCallback(){ //这里的position就是AsyncImageTask里面要用到的便到确定子线程通知主线程更新哪一行的ID.也就是下标 public void imageLoaded(Drawable image, int position) { if (image != null) { //获取刚才标识的组件,并更新 ImageView imageView = (ImageView) listView .findViewWithTag(position); if (imageView != null) { imageView.setImageDrawable(image); } } } }); //判断是否获取网络图片.如果还没有取到.就用本地的 if (drawable != null) { hold.image.setImageDrawable(drawable); } else { hold.image.setImageResource(R.drawable.ic_launcher); } hold.namtView.setText(book.getTitle()); hold.idView.setText(book.getId()); hold.idView.setVisibility(View.GONE); hold.introView.setText(book.getShortIntro()); return convertView; } @Override public int getCount() { return data.size(); } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } }
到这里关键的都说完了.Activity那里那就贴代码了
主要是什么时候实例MyListAdapter要注意一下
如果你的是像搜结果显示在listView中的这种.
那么MyListAdapter的数据源就要放在Button的点击响应事件里获取..然后通Message把数据源发送到handler中.在这handler中实例MyListAdapter对象.再绑定到listView.
顺便说一下.另一种情况
得到数据源.但ListView不显示内容.这是为什么 ?
一般有两种原因.
1, ListView不在handler中绑定数据..因为对组件的更新更改操作.一 定要在主线程中弄
2.就是布局问题.你的ListView里的item不指定高度.----这个是最常见的..ListView的item一定要指定高度.
就是你定义准备套在ListView中的那个layout_xxxx.xml这个文件中的LinearLayout这些要指定高度(最外面一层)
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。