Android ListView Optimizations Part 2: Displaying Images in Lists
Join the DZone community and get the full member experience.
Join For Freethis second post about listview optimizations will talk about the images in your lists. this post is an extension of the first, which was about the viewholder :
- android listview optimizations part 1: the viewholder : the viewholder is essential when you make a listview. see this post first.
i. the imageloader
we will create a class which will do all the jobs (here is the skeleton):
public class imageloader { public imageloader(context context) { } public void displayimage(string url, imageview imageview) { } }
and how to use it in your adapter:
public class myadapter extends baseadapter { private layoutinflater mlayoutinflater; private list mdata; private imageloader mimageloader; public myadapter(context context, list data){ mdata = data; mlayoutinflater = layoutinflater.from(context); mimageloader = new imageloader(context); } @override public int getcount() { return mdata == null ? : mdata.size(); } @override public mypojo getitem(int position) { return mdata.get(position); } @override public long getitemid(int position) { return position; } @override public view getview(int position, view view, viewgroup parent) { view vi = view; viewholder holder = null; if (vi == null) { vi = mlayoutinflater.inflate(r.layout.item, parent, false); holder = new viewholder(); holder.ivicon = (imageview) vi.findviewbyid(r.id.imageview_item_icon); vi.settag(holder); } else { holder = (viewholder) vi.gettag(); } mypojo item = getitem(position); mimageloader.displayimage(item.geticon(), holder.icon); return vi; } static class viewholder{ imageview ivicon; } }
a simple picture to understand the role of the imageloader:
and his code:
public class imageloader { private lrucache memorycache; private filecache filecache; private map imageviews = collections.synchronizedmap(new weakhashmap()); private drawable mstubdrawable; public imageloader(context context) { filecache = new filecache(context); init(context); } private void init(context context) { // get memory class of this device, exceeding this amount will throw an // outofmemory exception. final int memclass = ((activitymanager) context.getsystemservice( context.activity_service)).getmemoryclass(); // 1/8 of the available mem final int cachesize = 1024 * 1024 * memclass / 8; memorycache = new lrucache(cachesize); mstubdrawable = context.getresources().getdrawable(r.drawable.default_icon); } public void displayimage(string url, imageview imageview) { imageviews.put(imageview, url); bitmap bitmap = null; if (url != null && url.length() > ) bitmap = (bitmap) memorycache.get(url); if (bitmap != null) { //the image is in the lru cache, we can use it directly imageview.setimagebitmap(bitmap); } else { //the image is not in the lru cache //set a default drawable a search the image imageview.setimagedrawable(mstubdrawable); if (url != null && url.length() > ) queuephoto(url, imageview); } } private void queuephoto(string url, imageview imageview) { new loadbitmaptask().execute(url, imageview); } /** * search for the image in the device, then in the web * @param url * @return */ private bitmap getbitmap(string url) { bitmap ret = null; //from sd cache file f = filecache.getfile(url); if (f.exists()) { ret = decodefile(f); if (ret != null) return ret; } //from web try { //your requester will fetch the bitmap from the web and store it in the phone using the filecache ret = myrequester.getbitmapfromwebandstoreitinthephone(url); // your own requester here return ret; } catch ([exception][8] ex) { ex.printstacktrace(); return null; } } //decodes image and scales it to reduce memory consumption private bitmap decodefile(file f) { bitmap ret = null; try { [fileinputstream][9] is = new [fileinputstream][9](f); ret = bitmapfactory.decodestream(is, null, null); } catch ([filenotfoundexception][10] e) { e.printstacktrace(); } catch ([exception][8] e) { e.printstacktrace(); } return ret; } private class phototoload { public string url; public imageview imageview; public phototoload(string u, imageview i) { url = u; imageview = i; } } private boolean imageviewreused(phototoload phototoload) { //tag used here string tag = imageviews.get(phototoload.imageview); if (tag == null || !tag.equals(phototoload.url)) return true; return false; } class loadbitmaptask extends asynctask { private phototoload mphoto; @override protected transitiondrawable doinbackground([object][11]... params) { mphoto = new phototoload((string) params[], (imageview) params[1]); if (imageviewreused(mphoto)) return null; bitmap bmp = getbitmap(mphoto.url); if (bmp == null) return null; memorycache.put(mphoto.url, bmp); // transitiondrawable let you to make a crossfade animation between 2 drawables // it increase the sensation of smoothness transitiondrawable td = null; if (bmp != null) { drawable[] drawables = new drawable[2]; drawables[] = mstubdrawable; drawables[1] = new bitmapdrawable(application.getressources(), bmp); td = new transitiondrawable(drawables); td.setcrossfadeenabled(true); //important if you have transparent bitmaps } return td; } @override protected void onpostexecute(transitiondrawable td) { if (imageviewreused(mphoto)) { //imageview reused, just return return; } if (td != null) { // bitmap found, display it ! mphoto.imageview.setimagebitmap(drawable); mphoto.imageview.setvisibility(view.visible); //a little crossfade td.starttransition(200); } else { //bitmap not found, display the default drawable mphoto.imageview.setimagedrawable(mstubdrawable); } } } }
filecache:
public class filecache { private file cachedir; public filecache(context context) { this(context, ); } public filecache(context context, long evt) { //find the dir to save cached images cachedir = context.getcachedir(); if (!cachedir.exists()) cachedir.mkdirs(); } public file getfile(string url) { return new file(cachedir, string.valueof(url.hashcode())); } public void clear() { file[] files = cachedir.listfiles(); if (files == null) return; for (file f : files) f.delete(); } }
note 1 : the crossfade is done with a transitiondrawable , which give the feeling that your list scroll smoother!
note 2 : try to avoid the setimageresource(ind resid) as much as possible and create a default drawable when you init your imageloader. indeed, according to the doc :
this does bitmap reading and decoding on the ui thread, which can cause a latency hiccup. if that’s a concern, consider using
setimagedrawable(drawable)
orsetimagebitmap(bitmap)
andbitmapfactory
instead.
note 3 : we use the filecache here to get bitmaps, but we store the bitmaps thanks to our requester. you can do this very easily using an inputstream and an outputstream
ii. the blockingimageview
when you now scroll your list, you can notice some lag when images are displayed. this is due to the setimagedrawable(drawable d) wich can call a requestlayout (which can take some time).
there is a way to block the requestlayout, pretty simple. i found it thanks to jorim jaggy, on this google post . but look at the source code of the imageview first:
/** * sets a drawable as the content of this imageview. * * @param drawable the drawable to set */ public void setimagedrawable(drawable drawable) { if (mdrawable != drawable) { mresource = ; muri = null; int oldwidth = mdrawablewidth; int oldheight = mdrawableheight; updatedrawable(drawable); if (oldwidth != mdrawablewidth || oldheight != mdrawableheight) { requestlayout(); //here } invalidate(); } }
if you know that the dimension of all your images is the same, then you can block the requestlayout
by using a custom imageview
:
public class blockingimageview extends imageview { private boolean mblocklayout; public blockingimageview(context context) { super(context); } public blockingimageview(context context, attributeset attrs) { super(context, attrs); } public blockingimageview(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); } @override public void requestlayout() { if (!mblocklayout) { super.requestlayout(); } } @override public void setimageresource(int resid) { mblocklayout = true; super.setimageresource(resid); mblocklayout = false; } @override public void setimageuri(uri uri) { mblocklayout = true; super.setimageuri(uri); mblocklayout = false; } @override public void setimagedrawable(drawable drawable) { mblocklayout = true; super.setimagedrawable(drawable); mblocklayout = false; } }
don’t forget to modify the xml of your elements to add your custom imageview
.
you should now have a smooth listview with images.
hope you enjoyed, do not hesitate to leave comments and share it!
Published at DZone with permission of Antoine Merle, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Building a Flask Web Application With Docker: A Step-by-Step Guide
-
Observability Architecture: Financial Payments Introduction
-
Auditing Tools for Kubernetes
-
Scaling Site Reliability Engineering (SRE) Teams the Right Way
Comments