2012-03-23 98 views
3

我想用自定义的适配器(带图片和文本)显示ListView。ANDROID:在列表中加载异步图片

图像从远端服务器加载,所以我决定使用AsyncTask。

事实上,图片显示良好,但如果我迅速向下滚动,在1/2 secondes显示了错误的图片(加载后,会显示正确的图片)

这里是我的适配器代码:

public class GiAdapter extends BaseAdapter { 

    private Context mContext; 

    private List<SiteStaff> mListAppInfo; 
    private HashMap<Integer, ImageView> views; 
    private HashMap<String,Bitmap> oldPicts = new HashMap<String,Bitmap>(); 
    private LayoutInflater mInflater; 
    private boolean auto; 

    private final String BUNDLE_URL = "url"; 
    private final String BUNDLE_BM = "bm"; 
    private final String BUNDLE_POS = "pos"; 
    private final String BUNDLE_ID = "id"; 

    public GiAdapter(Context context, List<SiteStaff> list) { 
     mContext = context; 
     mListAppInfo = list; 
     views = new HashMap<Integer, ImageView>(); 
     mInflater = LayoutInflater.from(mContext); 

    } 

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

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

    @Override 
    public long getItemId(int position) { 
     return mListAppInfo.get(position).getId(); 
    } 

    @Override 
    public View getView(int position, View convertView, ViewGroup parent) { 

     LinearLayout layoutItem; 

     // reuse of convertView 
     if (convertView == null) { 
      layoutItem = (LinearLayout) mInflater.inflate(R.layout.auto_gi, parent, false); 
     } else { 
      layoutItem = (LinearLayout) convertView; 
     } 

     // infos for the current element 
     SiteStaff entry = mListAppInfo.get(position); 

     //set some text fields 
     TextView name = (TextView) layoutItem.findViewById(R.id.name); 
     TextView size = (TextView) layoutItem.findViewById(R.id.size); 

     name.setText(entry.getName()); 
     size.setText(entry.getSize()); 

     // get the imageView for the current object 
     ImageView v = (ImageView) layoutItem.findViewById(R.id.gi_image); 

     // put infos in bundle and send to the LoadImage class 
     Bundle b = new Bundle(); 

     //url of the pict 
     b.putString(BUNDLE_URL, entry.getUrl()); 

     //position in the listView 
     b.putInt(BUNDLE_POS, position); 

     //id of the current object 
     b.putInt(BUNDLE_ID, entry.getId()); 

     //put info in the map in order to display in the onPostExecute 
     views.put(position, v); 

     // thread 
     new LoadImage().execute(b); 

     return layoutItem; 

    } 

    //asyncTackClass for loadingpictures 
    private class LoadImage extends AsyncTask<Bundle, Void, Bundle> { 

     @Override 
     protected Bundle doInBackground(Bundle... b) { 

      Bitmap bm =null; 

      //cache: for better performance, check if url alredy exists 
      if(oldPicts.get(b[0].getString(BUNDLE_URL))==null){ 
       bm = Utils.getBitMapFromUrl(b[0].getString(BUNDLE_URL)); 
       oldPicts.put(b[0].getString(BUNDLE_URL),bm); 
      }else{ 
       bm = oldPicts.get(b[0].getString(BUNDLE_URL)); 
      } 

      // get info from bundle 
      Bundle bundle = new Bundle(); 
      bundle.putParcelable(BUNDLE_BM, bm); 
      bundle.putInt(BUNDLE_POS, b[0].getInt(BUNDLE_POS)); 

      return bundle; 

     } 

     @Override 
     protected void onPostExecute(Bundle result) { 
      super.onPostExecute(result); 

      //get picture saved in the map + set 
      ImageView view = views.get(result.getInt(BUNDLE_POS)); 
      Bitmap bm = (Bitmap) result.getParcelable(BUNDLE_BM); 

      if (bm != null){ //if bitmap exists... 
       view.setImageBitmap(bm); 
      }else{ //if not picture, display the default ressource 
       view.setImageResource(R.drawable.unknow); 
      } 

     } 

    } 

} 

谢谢!

回答

5

这也发生在我身上。我想到的是,在新图像绑定到图像视图之前,旧图像会显示一段时间。在你的情况下,发生什么事是你的正确的信息显示onPostExecute(),这需要一段时间,并在此之前,无论在convertview中设置的数据显示。有2种方式ü可以多为解决这个问题,

在你getView(),更改如果{} ...其他{}块只是

layoutItem = (LinearLayout) mInflater.inflate(R.layout.auto_gi, parent, false); 

设置与ImageView的getView()中的一些占位符图像直到onPostExecute()被调用。

希望这会有所帮助。

+0

谢谢〜我试着用你的代码和图片完美的显示! – johann 2012-03-23 02:08:39

+3

请注意,通过第一种方法,您基本上为列表视图的每一行膨胀视图(一个非常昂贵的操作),这意味着您的ListView不会顺利滚动。 – Christian 2012-04-18 20:42:55

1

当你扩展BaseAdapter并实现getView(int position,View convertView,ViewGroup parent)时,convertView实际上是一个循环视图。

这是视图被摧毁(最后一个视图被推离屏幕)。你可以做的是清除convertView,所以它不会显示陈旧的数据。也许用图像找到视图并执行layoutItem.removeAll()。希望这将清除意见,并给你一个干净的布局与合作。

+0

谢谢!它帮助我理解了BaseAdapter类的逻辑。 – johann 2012-03-23 02:10:34

6

就像Shubhayu提到的那样,您在ListView行中看到错误的图像,因为您的适配器正在回收一个视图来构造每一行,而您的异步任务保持对该再循环视图的引用。当你的任务完成时,他们将更新ImageView,这个实际上可能对应于其他行。

在每次调用getView时膨胀新视图的问题是,滚动时您的ListView性能不佳。你(正确)使用convertView来减少这种开销,你只是错过了将ImageView正确绑定到Async的一块。我在这里找到了一个解决方案:http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html

总之,它基本上扩展了Drawable来封装一个任务,并且只有在任务与图像绑定时才更新图像。 “处理并发性”部分解决了您所面临的问题。特别是,请阅读该部分之前的段落,了解导致问题的原因。