How do you efficiently load bitmaps from a dropdown folder into a ListView?

How do you upload images from drawable to the ListView folder in such a way that it happens quickly and does not use large amounts of RAM?

0
android android-listview
Oct. 19 '13 at 17:44
source share
2 answers

I played with loading some (rather large) images stored in the drawable folder in a ListView , and in this post I would like to share the result that I came to. Maybe I hope this saves someone a lot of time. I tested the code that I host on several Android 4+ devices, and I can say that it works quite smoothly, and the amount of RAM used remains relatively low. Some explanations are as follows:

  • we are expanding BaseAdapter
  • Images will be uploaded in the background using AsyncTask
  • as common to adapters of this type, we will use the ArrayList<> parameter parameterized with the Objects a custom class. In my application, this class is called Weapons.
  • we will scale the image depending on the screen size
  • we will apply the font to the TextView in each line of the list

Feel free to use this code for any purpose and modify it in any way. The only thing I ask is to check the code correctly before stating that something is not working. It works, believe me.

If you noticed any copy-paste errors (since I deleted some code that doesn't matter for this little tutorial), your feedback is welcome.

Before publishing the code, here is a small state diagram demonstrating the logic of the getView() method:

enter image description here

The code for the Adapter class is given below, I tried to explain everything you need in the comments:

 public class WeaponAdapter extends BaseAdapter implements View.OnClickListener { private ArrayList<Weapon> items; private LayoutInflater inflater = null; private WeaponHolder weaponHolder; private Weapon wp; private Context c; private Bitmap bmp; /*--- a simple View Holder class ---*/ static class WeaponHolder { public TextView text; public ImageView image, addFav; public AsyncImageSetter mImageLoader; } /*--- Context and all weapons of specified class are passed here ---*/ public WeaponAdapter(ArrayList<Weapon> items, Context c) { this.items = (ArrayList<Weapon>) items; inflater = LayoutInflater.from(c); this.c = c; } @Override public int getCount() { return items.size(); } @Override public Weapon getItem(int position) { return items.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { /*--- initialize our Weapon Object ---*/ wp = items.get(position); if (convertView == null) { /*--- no View is available. Inflate our list item layout and init the Views we need ---*/ convertView = inflater.inflate(R.layout.category_row, null); weaponHolder = new WeaponHolder(); weaponHolder.text = (TextView) convertView .findViewById(R.id.tvCatText); weaponHolder.image = (ImageView) convertView .findViewById(R.id.imgCatImage); weaponHolder.addFav = (ImageView) convertView .findViewById(R.id.imgAddFav); convertView.setTag(weaponHolder); } else { weaponHolder = (WeaponHolder) convertView.getTag(); /*--- if convertView is not null, cancel the current loading operation to * improve performance and decrease RAM usage ---*/ weaponHolder.mImageLoader.cancel(); } /*--- load the image in background ---*/ weaponHolder.mImageLoader = new AsyncImageSetter(c, weaponHolder.image, wp.getImage(), bmp, weaponHolder.text); weaponHolder.mImageLoader.execute(); weaponHolder.text.setText(wp.getName()); weaponHolder.addFav.setOnClickListener(this); return convertView; } @Override public void onClick(View v) { // do any stuff here } } 

Here is our AsyncTask , which will download and install images in the background.

NOTE. my weapon class has a getImage() method that returns resId from the drawable corresponding to the Object Weapon. You can change this part the way it works for you.

 public class AsyncImageSetter extends AsyncTask<Void, Void, Bitmap> { private ImageView img; private int image_resId; private Bitmap bmp; private Context c; private boolean cancel = false; private int sampleSize; private TextView txtGunName; private Typeface font; public AsyncImageSetter(Context c, ImageView img, int image_ResId, Bitmap bmp, TextView txtGunName) { this.img = img; this.image_resId = image_ResId; this.bmp = bmp; this.c = c; this.txtGunName = txtGunName; } public void cancel() { cancel = true; } @Override protected void onPreExecute() { /*--- we hide the Views from the user until the content is ready. This will prevent * the user from seeing an image being "transformed" into the next one (as a result of * View recycling) on slow devices. */ img.setVisibility(View.GONE); txtGunName.setVisibility(View.GONE); font = Typeface.createFromAsset(c.getAssets(), "b_reg.otf"); super.onPreExecute(); } @Override protected Bitmap doInBackground(Void... params) { if (!cancel) { try { return decodeAndScale(bmp); } catch (Exception e) { e.printStackTrace(); } } return null; } @Override protected void onPostExecute(Bitmap result) { img.setVisibility(View.VISIBLE); try { img.setImageBitmap(result); } catch (Exception e) { /*--- show an error icon in case something went wrong ---*/ img.setImageResource(R.drawable.ic_warn); } txtGunName.setVisibility(View.VISIBLE); txtGunName.setTypeface(font); super.onPostExecute(result); } private Bitmap decodeAndScale(Bitmap bmp) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = setSampleSize(); return BitmapFactory.decodeResource(c.getResources(), image_resId, options); } private int setSampleSize() { // TODO add multiple screens check /*--- modify this method to match your needs ---*/ if (GetSettings.getScreenWidth((Activity) c) >= 320) { /*--- physical width >= 480px ---*/ sampleSize = 2; } return sampleSize; }} 

You may have noticed that I am using the getScreenWidth() method from the GetSettings class. Its code is pretty simple and returns a dp value representing the width of the device screen:

  public static int getScreenWidth(Activity a) { Display display = a.getWindowManager().getDefaultDisplay(); DisplayMetrics outMetrics = new DisplayMetrics(); display.getMetrics(outMetrics); float density = a.getResources().getDisplayMetrics().density; float dpWidth = outMetrics.widthPixels / density; return (int) dpWidth; } 

Well, that’s all, and I hope this post really helped someone. Greetings.

PS if you are definitely sure that something is not working, most likely it was caused by the structure of your internal application, which is different from the one I use. In this case, I recommend that you follow these steps:

  • Ask a new question so you can add correctly formatted LogCat code and output
  • Notify me by adding a comment to my post. I will be happy to help you find out what is wrong
+14
Oct. 19 '13 at 17:45
source share

Use the scroll mode to set the images accordingly.

  • When viewing the list, do not set the images at the same time, as soon as the status is in standby mode, use your code to set the images. Thus, this will avoid the allocation of memory for those species that are not visible. If you try to set images while scrolling through the list, this may cause a memory error.
  • Also, make sure your drawable is a good size to fit. Retrieving images from drawable has always been quick, the main problem here will be memory usage.
  • Also create an array of drawings to set as a list item, so when setting up the adapter, you already have a hand full of stretchable objects that will be used for the list item and not fill out the list view with drawables based on the condition
+1
Oct 19 '13 at 18:06 on
source share



All Articles