在Android(初学者级别)Listview中的懒加载图像?

在Android(初学者级别)Listview中的懒加载图像?

问题描述:

可能重复:
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。这是加载图像的正确顺序。但不是列表中的所有项目。固体项目间隔之间发生不匹配。 如何纠正它?

普利文 -

正如你已经发现这个我的博客文章,我只是想将其推回#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

据我所见,静态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

啊,请检查您的logcat,以确保您的应用程序ISN没有被杀害和重新启动。大多数手机将您的总应用程序大小限制为16mb或24mb。很容易加载一堆图像,运行,杀死,重新启动,并让onPause不在屏幕上加载大数据。这是穷人的garabage集合。

+0

我编辑了我的问题。请看看它。 – Praveen 2010-05-31 08:22:27

据我所知,您需要在滚动完成后更新您的列表。这很容易。下面是固定的代码为您提供:

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中显示。

我明白了。这是我想要的完美代码。延迟加载工作到自定义适配器只是可见列表项的图标。希望对初学者有所帮助

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