2010-05-26 69 views
39

可能重複:
Android - How do I do a lazy load of images in ListView在Android(初學者級別)Listview中的懶加載圖像?

我的工作與自定義適配器列表視圖。我想加載它的圖像和文本視圖。這些圖像是從互聯網網址加載的。我只是想顯示可見列表項的圖像來激發用戶。我提到了Shelves opensource project example from romainguy,但它很難理解代碼。對於初學者級別,我只想知道如何處理適配器和活動之間的標籤。從commonswareexample我可以在適配器上設置標籤,但不能在列表視圖的空閒狀態下顯示相應的圖像。請幫助我提出你的想法。示例代碼更容易理解。

謝謝。

編輯:

EfficientSlow適配器在ApiDemos的組合是更有助於理解。

變化對有效的適配器例如做過這樣的:

public class List14 extends ListActivity implements ListView.OnScrollListener { 
// private TextView mStatus; 

private static boolean mBusy = false; 
static ViewHolder holder; 

public static class EfficientAdapter extends BaseAdapter { 
    private LayoutInflater mInflater; 
    private Bitmap mIcon1; 
    private Bitmap mIcon2; 

    public EfficientAdapter(Context context) { 
     // Cache the LayoutInflate to avoid asking for a new one each time. 
     mInflater = LayoutInflater.from(context); 

     // Icons bound to the rows. 
     mIcon1 = BitmapFactory.decodeResource(context.getResources(), 
       R.drawable.icon48x48_1); 
     mIcon2 = BitmapFactory.decodeResource(context.getResources(), 
       R.drawable.icon48x48_2); 
    } 

    /** 
    * The number of items in the list is determined by the number of 
    * speeches in our array. 
    * 
    * @see android.widget.ListAdapter#getCount() 
    */ 
    public int getCount() { 
     return DATA.length; 
    } 

    /** 
    * Since the data comes from an array, just returning the index is 
    * sufficent to get at the data. If we were using a more complex data 
    * structure, we would return whatever object represents one row in the 
    * list. 
    * 
    * @see android.widget.ListAdapter#getItem(int) 
    */ 
    public Object getItem(int position) { 
     return position; 
    } 

    /** 
    * Use the array index as a unique id. 
    * 
    * @see android.widget.ListAdapter#getItemId(int) 
    */ 
    public long getItemId(int position) { 
     return position; 
    } 

    /** 
    * Make a view to hold each row. 
    * 
    * @see android.widget.ListAdapter#getView(int, android.view.View, 
    *  android.view.ViewGroup) 
    */ 
    public View getView(int position, View convertView, ViewGroup parent) { 
     // A ViewHolder keeps references to children views to avoid 
     // unneccessary calls 
     // to findViewById() on each row. 

     // When convertView is not null, we can reuse it directly, there is 
     // no need 
     // to reinflate it. We only inflate a new View when the convertView 
     // supplied 
     // by ListView is null. 
     if (convertView == null) { 
      convertView = mInflater.inflate(R.layout.list_item_icon_text, 
        null); 

      // Creates a ViewHolder and store references to the two children 
      // views 
      // we want to bind data to. 
      holder = new ViewHolder(); 
      holder.text = (TextView) convertView.findViewById(R.id.text); 
      holder.icon = (ImageView) convertView.findViewById(R.id.icon); 

      convertView.setTag(holder); 
     } else { 
      // Get the ViewHolder back to get fast access to the TextView 
      // and the ImageView. 
      holder = (ViewHolder) convertView.getTag(); 
     } 
     if (!mBusy) { 
      holder.icon.setImageBitmap(mIcon1); 

      // Null tag means the view has the correct data 
      holder.icon.setTag(null); 
     } else { 
      holder.icon.setImageBitmap(mIcon2); 

      // Non-null tag means the view still needs to load it's data 
      holder.icon.setTag(this); 
     } 
     holder.text.setText(DATA[position]); 

     // Bind the data efficiently with the holder. 
     // holder.text.setText(DATA[position]); 

     return convertView; 
    } 

    static class ViewHolder { 
     TextView text; 
     ImageView icon; 
    } 
} 

private Bitmap mIcon1; 
private Bitmap mIcon2; 
@Override 
public void onCreate(Bundle savedInstanceState) { 
    mIcon1 = BitmapFactory.decodeResource(this.getResources(), 
      R.drawable.icon48x48_1); 
    mIcon2 = BitmapFactory.decodeResource(this.getResources(), 
      R.drawable.icon48x48_2); 
    super.onCreate(savedInstanceState); 
    setListAdapter(new EfficientAdapter(this)); 
    getListView().setOnScrollListener(this); 
} 

public void onScroll(AbsListView view, int firstVisibleItem, 
     int visibleItemCount, int totalItemCount) { 
} 

public void onScrollStateChanged(AbsListView view, int scrollState) { 
    switch (scrollState) { 
    case OnScrollListener.SCROLL_STATE_IDLE: 
     mBusy = false; 

     int first = view.getFirstVisiblePosition(); 
     int count = view.getChildCount(); 
     for (int i = 0; i < count; i++) { 

      holder.icon = (ImageView) view.getChildAt(i).findViewById(
        R.id.icon); 
      if (holder.icon.getTag() != null) { 
       holder.icon.setImageBitmap(mIcon1); 
       holder.icon.setTag(null); 
      } 
     } 

     // mStatus.setText("Idle"); 
     break; 
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: 
     mBusy = true; 
     // mStatus.setText("Touch scroll"); 
     break; 
    case OnScrollListener.SCROLL_STATE_FLING: 
     mBusy = true; 
     // mStatus.setText("Fling"); 
     break; 
    } 
} 
private static final String[] DATA = { "Abbaye de Belloc", 
     "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", 
     "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu"}; 
} 

現在工作得很好。但是,當滾動狀態不正確重新加載圖像。項目的某些間隔不顯示圖像2。這是加載圖像的正確順序。但不是列表中的所有項目。固體項目間隔之間發生不匹配。 如何糾正它?

回答

5

普利文 -

正如你已經發現這個我的博客文章,我只是想將其推回#1,以便其他人可以使用它。

這裏的基本討論: http://ballardhack.wordpress.com/2010/04/05/loading-remote-images-in-a-listview-on-android/

而且還有一類我記錄後,它使用一個線程,並回調加載圖像:

http://ballardhack.wordpress.com/2010/04/10/loading-images-over-http-on-a-separate-thread-on-android/

更新:爲了解決您的特定的例外情況,我認爲從列表getChildAt返回的視圖不是ImageView - 它是用於保存圖像和文本的任何佈局視圖。

更新包括相關代碼:(每@喬治收納器的建議)

下面是我用的適配器:

public class MediaItemAdapter extends ArrayAdapter<MediaItem> { 
    private final static String TAG = "MediaItemAdapter"; 
    private int resourceId = 0; 
    private LayoutInflater inflater; 
    private Context context; 

    private ImageThreadLoader imageLoader = new ImageThreadLoader(); 

    public MediaItemAdapter(Context context, int resourceId, List<MediaItem> mediaItems) { 
    super(context, 0, mediaItems); 
    this.resourceId = resourceId; 
    inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    this.context = context; 
    } 

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

    View view; 
    TextView textTitle; 
    TextView textTimer; 
    final ImageView image; 

    view = inflater.inflate(resourceId, parent, false); 

    try { 
     textTitle = (TextView)view.findViewById(R.id.text); 
     image = (ImageView)view.findViewById(R.id.icon); 
    } catch(ClassCastException e) { 
     Log.e(TAG, "Your layout must provide an image and a text view with ID's icon and text.", e); 
     throw e; 
    } 

    MediaItem item = getItem(position); 
    Bitmap cachedImage = null; 
    try { 
     cachedImage = imageLoader.loadImage(item.thumbnail, new ImageLoadedListener() { 
     public void imageLoaded(Bitmap imageBitmap) { 
     image.setImageBitmap(imageBitmap); 
     notifyDataSetChanged();    } 
     }); 
    } catch (MalformedURLException e) { 
     Log.e(TAG, "Bad remote image URL: " + item.thumbnail, e); 
    } 

    textTitle.setText(item.name); 

    if(cachedImage != null) { 
     image.setImageBitmap(cachedImage); 
    } 

    return view; 
    } 
} 
+0

你知道如何獲得圖像視圖對象嗎?我看到並使用了你的代碼。但是,沒有正確加載圖像。一個或兩個圖像沒有完成丟失。並獲得Outofmemory異常。爲什麼?願你呢? – Praveen 2010-05-28 08:46:40

+0

您需要跟蹤您的視圖層次結構。在ListView上調用getChildAt(index)將返回可見列表頂部索引的列表視圖項(佈局視圖)。在該視圖對象上,您可以調用findViewById來獲取圖像視圖。 – jwadsack 2010-05-28 19:42:26

+0

請從您的博客相關部分到這裏。沒有這一點,當這些鏈接死亡時,你的答案的有效性就會隨之消失。 – 2012-07-02 23:55:13

0

據我所見,靜態ViewHolder沒有任何幫助。嘗試將整個onScrollStateChanged函數放在/**/之間,刪除static ViewHolder行,並將holder = new ViewHolder();更改爲ViewHolder holder = new ViewHolder();

+0

「試着將整個onScrollStateChanged函數放在/ *和* /」之間。它沒有任何意義。那麼我們如何跟蹤滾動狀態以加載可見的特定行。 – Praveen 2010-05-27 08:10:20

+0

它們在適配器的getView函數中加載。 – 2010-05-27 20:42:22

0

啊,請檢查您的logcat,以確保您的應用程序ISN沒有被殺害和重新啓動。大多數手機將您的總應用程序大小限制爲16mb或24mb。很容易加載一堆圖像,運行,殺死,重新啓動,並讓onPause不在屏幕上加載大數據。這是窮人的garabage集合。

+0

我編輯了我的問題。請看看它。 – Praveen 2010-05-31 08:22:27

7

據我所知,您需要在滾動完成後更新您的列表。這很容易。下面是固定的代碼爲您提供:

EfficientAdapter adapter; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    mIcon1 = BitmapFactory.decodeResource(this.getResources(), 
      R.drawable.icon48x48_1); 
    mIcon2 = BitmapFactory.decodeResource(this.getResources(), 
      R.drawable.icon48x48_2); 
    super.onCreate(savedInstanceState); 
    adapter=new EfficientAdapter(this); 
    setListAdapter(adapter); 
    getListView().setOnScrollListener(this); 
} 

public void onScroll(AbsListView view, int firstVisibleItem, 
    int visibleItemCount, int totalItemCount) { 
} 

public void onScrollStateChanged(AbsListView view, int scrollState) { 
    switch (scrollState) { 
    case OnScrollListener.SCROLL_STATE_IDLE: 
     mBusy = false; 
     adapter.notifyDataSetChanged() 
     break; 
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: 
     mBusy = true; 
     // mStatus.setText("Touch scroll"); 
     break; 
    case OnScrollListener.SCROLL_STATE_FLING: 
     mBusy = true; 
     // mStatus.setText("Fling"); 
     break; 
    } 
} 

notifyDataSetChanged會告訴適配器重新顯示所有可見的物品,所以他們會與圖像2中顯示。

17

我明白了。這是我想要的完美代碼。延遲加載工作到自定義適配器只是可見列表項的圖標。希望對初學者有所幫助

public class List14 extends ListActivity implements ListView.OnScrollListener { 
// private TextView mStatus; 

private static boolean mBusy = false; 
static ViewHolder holder; 

public static class EfficientAdapter extends BaseAdapter { 
    private LayoutInflater mInflater; 
    private Bitmap mIcon1; 
    private Bitmap mIcon2; 
    private Context mContext; 

    public EfficientAdapter(Context context) { 
     // Cache the LayoutInflate to avoid asking for a new one each time. 
     mInflater = (LayoutInflater) context 
       .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     mContext = context; 
     // Icons bound to the rows. 
     mIcon1 = BitmapFactory.decodeResource(context.getResources(), 
       R.drawable.icon48x48_1); 
     mIcon2 = BitmapFactory.decodeResource(context.getResources(), 
       R.drawable.icon48x48_2); 
    } 

    /** 
    * The number of items in the list is determined by the number of 
    * speeches in our array. 
    * 
    * @see android.widget.ListAdapter#getCount() 
    */ 
    public int getCount() { 
     return DATA.length; 
    } 

    /** 
    * Since the data comes from an array, just returning the index is 
    * sufficent to get at the data. If we were using a more complex data 
    * structure, we would return whatever object represents one row in the 
    * list. 
    * 
    * @see android.widget.ListAdapter#getItem(int) 
    */ 
    public Object getItem(int position) { 
     return position; 
    } 

    /** 
    * Use the array index as a unique id. 
    * 
    * @see android.widget.ListAdapter#getItemId(int) 
    */ 
    public long getItemId(int position) { 
     return position; 
    } 

    /** 
    * Make a view to hold each row. 
    * 
    * @see android.widget.ListAdapter#getView(int, android.view.View, 
    *  android.view.ViewGroup) 
    */ 
    public View getView(int position, View convertView, ViewGroup parent) { 
     // A ViewHolder keeps references to children views to avoid 
     // unneccessary calls 
     // to findViewById() on each row. 

     // When convertView is not null, we can reuse it directly, there is 
     // no need 
     // to reinflate it. We only inflate a new View when the convertView 
     // supplied 
     // by ListView is null. 
     if (convertView == null) { 
      convertView = mInflater.inflate(R.layout.list_item_icon_text, 
        parent, false); 

      // Creates a ViewHolder and store references to the two children 
      // views 
      // we want to bind data to. 
      holder = new ViewHolder(); 
      holder.text = (TextView) convertView.findViewById(R.id.text); 
      holder.icon = (ImageView) convertView.findViewById(R.id.icon); 

      convertView.setTag(holder); 
     } else { 
      // Get the ViewHolder back to get fast access to the TextView 
      // and the ImageView. 
      holder = (ViewHolder) convertView.getTag(); 
     } 

     if (!mBusy) { 

      holder.icon.setImageBitmap(mIcon1); 

      // Null tag means the view has the correct data 
      holder.icon.setTag(null); 

     } else { 
      holder.icon.setImageBitmap(mIcon2); 

      // Non-null tag means the view still needs to load it's data 
      holder.icon.setTag(this); 
     } 
     holder.text.setText(DATA[position]); 

     // Bind the data efficiently with the holder. 
     // holder.text.setText(DATA[position]); 

     return convertView; 
    } 

    static class ViewHolder { 
     TextView text; 
     ImageView icon; 
    } 
} 

private Bitmap mIcon1; 
private Bitmap mIcon2; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    mIcon1 = BitmapFactory.decodeResource(this.getResources(), 
      R.drawable.icon48x48_1); 
    mIcon2 = BitmapFactory.decodeResource(this.getResources(), 
      R.drawable.icon48x48_2); 
    super.onCreate(savedInstanceState); 
    setListAdapter(new EfficientAdapter(this)); 
    getListView().setOnScrollListener(this); 
} 

public void onScroll(AbsListView view, int firstVisibleItem, 
     int visibleItemCount, int totalItemCount) { 
} 

public void onScrollStateChanged(AbsListView view, int scrollState) { 
    switch (scrollState) { 
    case OnScrollListener.SCROLL_STATE_IDLE: 
     mBusy = false; 

     int first = view.getFirstVisiblePosition(); 
     int count = view.getChildCount(); 

     for (int i = 0; i < count; i++) { 

      holder.icon = (ImageView) view.getChildAt(i).findViewById(
        R.id.icon); 
      if (holder.icon.getTag() != null) { 
       holder.icon.setImageBitmap(IMAGE[first+i]);// this is the image url array. 
       holder.icon.setTag(null); 
      } 
     } 

     // mStatus.setText("Idle"); 
     break; 
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: 
     mBusy = true; 
     // mStatus.setText("Touch scroll"); 
     break; 
    case OnScrollListener.SCROLL_STATE_FLING: 
     mBusy = true; 
     // mStatus.setText("Fling"); 
     break; 
    } 
} 

private static final String[] DATA = { "Abbaye de Belloc", 
     "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", 
     "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", 
     "Yarra Valley Pyramid", "Yorkshire Blue", "Zamorano", 
     "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano" }; 
    } 
+4

有點奇怪,你使用持有人,然後總是使用findViewById ...你可以簡化,如果你避免持有人的事情或更快,如果你在持有人的幫助下設置的東西... ... – Karussell 2011-04-08 10:07:17