在android(初级)的Listview上延迟加载图像? [复制]

Posted

技术标签:

【中文标题】在android(初级)的Listview上延迟加载图像? [复制]【英文标题】:Lazy Load images on Listview in android(Beginner Level)? [duplicate] 【发布时间】:2011-02-24 02:51:23 【问题描述】:

可能重复:android - How do I do a lazy load of images in ListView

我正在使用自定义适配器处理列表视图。我想在上面加载图像和文本视图。图片是从互联网网址加载的。我只想向 hte 用户显示可见列表项的图像。我参考了Shelves opensource project example from romainguy,但理解代码很复杂。对于初学者来说,我只想知道如何处理适配器和活动之间的标签。从commonsware example 我可以在适配器上设置标签,但在列表视图的空闲状态下无法显示相应的图像。请帮我谈谈你的想法。示例代码更容易理解。

谢谢。

编辑:

ApiDemos中Efficient和SlowAdapter的组合更有助于理解。

对高效适配器示例所做的更改如下:

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】:

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

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" ;
  

【讨论】:

你使用一个持有人然后总是使用 findViewById 有点奇怪......如果你避免那个持有人的东西,或者如果你在持有人的帮助下设置东西,你可以让它变得简单...【参考方案2】:

普雷文-

正如您已经找到我的博客文章一样,我只是想将它推回 ***,以便其他人可以使用它。

以下是基本讨论: 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 - 它是您用来保存图像的任何布局视图,并且文本。

更新以包含相关代码:(根据@george-stocker 的建议)

这是我使用的适配器:

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;
  

【讨论】:

【参考方案3】:

据我了解,您需要在滚动完成后更新您的列表。这很简单。这是给你的固定代码:

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 将告诉适配器重新显示所有可见项目,因此它们将与 image2 一起显示。

【讨论】:

【参考方案4】:

据我所知,静态 ViewHolder 没有任何帮助。尝试将整个onScrollStateChanged 函数放在/**/ 之间,删除static ViewHolder 行,并将holder = new ViewHolder(); 更改为ViewHolder holder = new ViewHolder();

【讨论】:

【参考方案5】:

啊,检查您的 logcat 以确保您的应用没有被杀死并重新启动。大多数手机将您的总应用程序大小限制为 16mb 或 24mb。加载一堆图像、运行、被杀死、重新启动以及让您的 onPause 不会将大数据加载到屏幕外很容易。这是穷人的垃圾收集。

【讨论】:

以上是关于在android(初级)的Listview上延迟加载图像? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Android - ListView 中的延迟点击

Android中的Listview延迟加载平滑[重复]

android新闻客户端问题

android 怎么在listview周围加一个框

Flutter ListView 双向延迟加载(上、下)

android listview如何按时加滚动到顶部?