ListView-3异步加载图片并防止错位与闪烁

网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作.

如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题。

我简单分析一下:

当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView.

当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Item8 复用的是

Item1 的 view 如果没有异步不会有任何问题,虽然 Item8 和 Item1 指向的是同一个 view,但滑到

Item8 时刷上了 Item8 的数据,这时 Item1 的数据和 Item8 是一样的,因为它们指向的是同一块内存,

但 Item1 已滚出了屏幕你看不见。当 Item1 再次可见时这块 view 又涮上了 Item1 的数据。

 

但当有异步下载时就有问题了,假设 Item1 的图片下载的比较慢,Item8 的图片下载的比较快,你滚上去

使 Item8 可见,这时 Item8 先显示它自己下载的图片没错,但等到 Item1 的图片也下载完时你发现

Item8 的图片也变成了 Item1 的图片,因为它们复用的是同一个 view。 如果 Item1 的图片下载的比

Item8 的图片快, Item1 先刷上自己下载的图片,这时你滑下去,Item8 的图片还没下载完, Item8

会先显示 Item1 的图片,因为它们是同一快内存,当 Item8 自己的图片下载完后 Item8 的图片又刷成

了自己的,你再滑上去使 Item1 可见, Item1 的图片也会和 Item8 的图片是一样的,

因为它们指向的是同一块内存。

 

最简单的解决方法就是网上说的,给 ImageView 设置一个 tag, 并预设一个图片。

当 Item1 比 Item8 图片下载的快时, 你滚下去使 Item8 可见,这时 ImageView 的 tag 被设成了

Item8 的 URL, 当 Item1 下载完时,由于 Item1 不可见现在的 tag 是 Item8 的 URL,所以不满足条件,

虽然下载下来了但不会设置到 ImageView 上, tag 标识的永远是可见 view 中图片的 URL。

代码如下:

MainActivity.java  (切记图片用src,不要用背景图片属性)

/*
ListView的数据错位和闪烁问题:
http://openapi.db.39.net/app/GetDrugCompany?sign=9DFAAD5404FCB6168EA6840DCDFF39E5&app_key=app
     图片错位--例如:当我们首先显示item1时,会发送网络请求异步下载图片,当图片还没有下载完时,用户滑动了屏幕item1从屏幕中消失,
             新的那个itme会 复用item1,而item的控件的引用时复用的item1,这时item1图片下载完成,设置图片的时候,会设置到item上,就错位了
                       即:本身的图片下载慢于复用的那张,所以就先把复用的设置到了这个item上
      图片闪烁--在错位的基础上,item显示item1图片的的时候,此时item本身的图片也下载完成,会再次将自己的图片设置给item,就闪烁了
          即:复用的图片刚刚设置到该imageView上,自己的图片也下载好了,这时再次设置,就造成了图片的闪烁
 根据出现这种现象的根本,显而易见解决的问题就是在下载item的时候,不让item1设置上来,也就需要为这个图片设置标签,让它对号入座
这里的标签设置为该图片对象 对应的标题,所以只有当这个图片的标签跟标题一致时才能设置到imageView上去
 */
public class MainActivity extends Activity implements CallbackData{
    private ListView listView;
    private ArrayList<Integer> list =new ArrayList<Integer>();
    private String path="http://openapi.db.39.net/app/GetDrugCompany?sign=9DFAAD5404FCB6168EA6840DCDFF39E5&app_key=app";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MedicineAsyncTask(MainActivity.this).execute(path);
        listView=(ListView) findViewById(R.id.listView);
       
    }
    
    /**
     * 解析完数据的回调方法
     * @param result 
     */
    @Override
    public void upData(ArrayList<Medicine> result) {
        if(result!=null && result.size()>0){
            //将从网络上下载的数据,解析完添加到适配器
             MedicineAdapter adapter=new MedicineAdapter(this,result);
             listView.setAdapter(adapter);
        }else{
            Toast.makeText(this, "数据下载失败", 0).show();
        }
    }   
}

MedicineAsyncTask.java

/**
 *异步获取网络数据和解析json数据,返回装药对象的集合
 */
public class MedicineAsyncTask extends AsyncTask<String, Void, ArrayList<Medicine>>{
    private Context context;
    private ProgressDialog progressDialog;
    
    public MedicineAsyncTask(Context context) {
        this.context = context;
    }
    /**
     * 准备工作(主线程)
     */
    @Override
    protected void onPreExecute() {
        progressDialog = new ProgressDialog(context);
        progressDialog.setTitle("提示框!");
        progressDialog.setMessage("正在拼命加载数据...");
        progressDialog.show();
    }
    /**
     * 后台下载
     */
    @Override
    protected ArrayList<Medicine> doInBackground(String... params) {
        String jsonData = HttpUtils.getJsonDataFromNet(params[0]);//得到json数据
        return parserJsonData(jsonData);
    }
    //解析数据
    private ArrayList<Medicine> parserJsonData(String jsonData) {
        ArrayList<Medicine> list=new ArrayList<Medicine>();
        try {
            JSONObject jsonObject=new JSONObject(jsonData);
            JSONArray jsonArray = jsonObject.getJSONArray("results");
            for (int i = 0; i < jsonArray.length(); i++) {
                String namecn = jsonArray.getJSONObject(i).getString("namecn");
                String titleimg=jsonArray.getJSONObject(i).getString("titleimg");
                list.add(new Medicine(namecn, titleimg));
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return list;
    }
    
    /**
     * 后台下载完成(主线程)
     */
    @Override
    protected void onPostExecute(ArrayList<Medicine> result) {
        progressDialog.dismiss();//不管下没下成功都需要关闭提示框
        ((MainActivity)context).upData(result);
    }
}

CallbackData.java

//将后台下载好的数据返回给主线程
public interface CallbackData {
    void upData(ArrayList<Medicine> result);
}

适配器MedicineAdapter.java

/**
 * 解决了错位问题,但是这样每次滑动后都需要重新去下载网络数据
 */
public class MedicineAdapter extends BaseAdapter{
    private ArrayList<Medicine> list;
    private Context context;
    
    
    public MedicineAdapter( Context context,ArrayList<Medicine> list) {
        this.list = list;
        this.context = context;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int position){
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, final ViewGroup parent) {
        final ViewHolder holder;
        if(convertView==null){
            convertView=View.inflate(context, R.layout.item_layout, null);
            holder=new ViewHolder();
            holder.img=(ImageView) convertView.findViewById(R.id.imageView);
            holder.text=(TextView) convertView.findViewById(R.id.textView);
            convertView.setTag(holder);
        }else{
            holder= (ViewHolder) convertView.getTag();
        }
        holder.text.setText(list.get(position).getNamecn());//设置文本
        //复用convertView容易产生图片错乱和闪烁问题(因为是异步下载图片)
        //不设置不然显示的是之前复用的数据,设置默认图片(这里不能设置为背景图片,因为目的是要覆盖该img之前显示的图片)
        holder.img.setImageResource(R.drawable.ic_launcher);//如果是背景,这个img本来显示的就在它的上面,最后还是会有错位现象
        // 给 ImageView 设置一个 tag 将该图片的名字设置为该图片的唯一标签
        holder.img.setTag(list.get(position).getTitleimg());
        //下载图片
        new LoadImgAsyncTask(context,new UpdataImageView() {
            /**
             * 下载图片完成调用该方法(主线程运行)
             */
            @Override
            public void updataImg(Bitmap result) {
                //找到现在这个item的图片标签,如果找不到就是null
                ImageView img = (ImageView) parent.findViewWithTag(list.get(position).getTitleimg());
                if(img!=null){
                    img.setImageBitmap(result);
                }
            }
        }).execute(list.get(position).getTitleimg());
        
        return convertView;
    }
    
    class ViewHolder{
        private ImageView img;
        private TextView text;
    }
}

LoadImgAsyncTask.java

/**
 *异步获取网络图片,并返回图片给主线程
 */
public class LoadImgAsyncTask extends AsyncTask<String, Void, Bitmap>{
    private Context context;
    /**
     * 接口,实现数据的传递
     */
    private UpdataImageView updataImg;
    
    public LoadImgAsyncTask(Context context,UpdataImageView updataImg) {
        this.context = context;
        this.updataImg=updataImg;
    }
    
    /**
     * 后台下载
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        Bitmap bitmap = HttpUtils.getBitmapDataFromNet(params[0]);//得到json数据
        return bitmap;
    }
    
    /**
     * 后台下载完成(主线程)
     */
    @Override
    protected void onPostExecute(Bitmap result) {
        updataImg.updataImg(result);
    }
}

 接口回调,实现数据的传输 UpdataImageView.java

/**
 * 更新下载的图片的显示
 */
public interface UpdataImageView {
    public void updataImg(Bitmap result);
}

 

药的实体类  Medicine.java

public class Medicine {
    /**
     * 药名
     */
    private String namecn;
    /**
     * 图片地址
     */
    private String titleimg;
    
    
    public Medicine() {
    }
    public Medicine(String namecn, String titleimg) {
        this.namecn = namecn;
        this.titleimg = titleimg;
    }
    public String getNamecn() {
        return namecn;
    }
    public void setNamecn(String namecn) {
        this.namecn = namecn;
    }
    public String getTitleimg() {
        return titleimg;
    }
    public void setTitleimg(String titleimg) {
        this.titleimg = titleimg;
    }
    @Override
    public String toString() {
        return "Medicine [namecn=" + namecn + ", titleimg=" + titleimg + "]";
    }
    
}

HttpUtils.java

public class HttpUtils {
    
    /**
     * 获取字符串
     * @param path
     * @return
     */
    public static String getJsonDataFromNet(String path){
        byte[] byteArrayDataFromNet = getByteArrayDataFromNet(path);
        if(byteArrayDataFromNet != null && byteArrayDataFromNet.length != 0){
            return new String(byteArrayDataFromNet);
        }
        return null;
        
    }
    
    //获得字节数组
    
        public static byte[] getByteArrayDataFromNet(String path){
            try {
                DefaultHttpClient client = new DefaultHttpClient();
                HttpGet httpGet = new HttpGet(path);
                HttpResponse httpResponse = client.execute(httpGet);
                if (200 == httpResponse.getStatusLine().getStatusCode()) {
                    HttpEntity entity = httpResponse.getEntity();
                   byte[] byteArray = EntityUtils.toByteArray(entity);
                   return byteArray;
                }
                
            } catch (ClientProtocolException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            return null;
        }
        
    //获取bitmap    
        public static Bitmap getBitmapDataFromNet(String path){
            byte[] byteArrayDataFromNet = getByteArrayDataFromNet(path);
            if(byteArrayDataFromNet != null && byteArrayDataFromNet.length != 0){
                return BitmapFactory.decodeByteArray(byteArrayDataFromNet, 0, byteArrayDataFromNet.length);
            }
            return null;
        }
}

 效果图:(可见,不会出现图片错位了,但是每次都得重新下载图片,所以还需要去保存图片)

      

posted @ 2016-08-06 09:19  ts-android  阅读(698)  评论(0编辑  收藏  举报