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

可能重复:
Android的 – 我如何在ListView中做一个懒惰的图像加载

我正在使用自定义适配器的列表视图。 我想加载它的图像和文本视图。 图片是从互联网上的url加载。 我只是想显示可见列表项的图像,以使用户。 我引用了来自romainguy的Shelves开源项目实例 ,但是它对代码的理解很复杂。 对于初学者来说,我只想知道如何处理适配器和活动之间的标签。 从commonsware的 例子中,我可以在适配器上设置标签,但不能在listview的空闲状态下显示相应的图像。 请帮助我的想法。 示例代码更容易理解。

谢谢。

编辑:

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

有效的适配器例如这样做的变化:

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

Praveen –

正如你已经发现我的博客文章,我只是想推回到Stackoverflow,以便其他人可以使用它。

这里是基本的讨论: http : //ballardhack.wordpress.com/2010/04/05/loading-remote-images-in-a-listview-on-android/

还有一个我稍后logging的类使用线程和callback加载图像:

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

更新:为了解决您的特定exception,我认为从getChildAt返回列表中的视图不是一个ImageView – 它是任何布局视图你用来举行图像和文字。

更新包括相关的代码 :(根据@乔治储备的build议)

这是我使用的适配器:

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

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

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

据我了解,滚动完成后需要更新列表。 这很容易。 这里是你的固定代码:

 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一起显示。

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

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