Hi all
This example shows you how to load large images in the gridview without any lag or without outofmemory error.
This sample application uses the LruCache to hold the images.
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.
Read more from here.
http://developer.android.com/reference/android/util/LruCache.html
Now we will go to the implementation.
This is the MainActivity that holds the GridView.
We are adding here 100 images in the Grid to load.
This time I am loading images from the resources.
Everything is explained with in the code itself, So I am not adding more description here
MainActivity.java
package com.coderzheaven.testlistview; import java.util.ArrayList; import android.app.Activity; import android.os.Bundle; import android.widget.GridView; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ArrayList<String> items = new ArrayList<String>(); // adding 100 images for (int p = 0; p < 100; p++) { items.add("photo"); } ListAdapter list = new ListAdapter(this, items); GridView grid = new GridView(this); grid.setAdapter(list); grid.setNumColumns(GridView.AUTO_FIT); setContentView(grid); } }
OK Now that our Activity is complete we will see the ListAdapter class that is processing each image and helps loading fast.
ListAdapter.java
package com.coderzheaven.testlistview; import java.lang.ref.WeakReference; import java.util.ArrayList; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.util.LruCache; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridLayout.LayoutParams; import android.widget.GridView; import android.widget.ImageView; public class ListAdapter extends BaseAdapter { Context context; ArrayList<String> items; private LruCache<String, Bitmap> mMemoryCache; public ListAdapter(Context context, ArrayList<String> items) { this.context = context; this.items = items; // Get memory class of this device, exceeding this amount will throw an // OutOfMemory exception. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8th of the available memory for this memory cache. final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number // of items. return bitmap.getByteCount(); } }; } @Override public int getCount() { return items.size(); } @Override public Object getItem(int arg0) { return items.get(arg0); } @Override public long getItemId(int arg0) { return arg0; } @Override public View getView(int arg0, View convertView, ViewGroup arg2) { ImageView img = null; if (convertView == null) { img = new ImageView(context); img.setScaleType(ImageView.ScaleType.CENTER_CROP); img.setLayoutParams(new GridView.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } else { img = (ImageView) convertView; } int resId = context.getResources().getIdentifier(items.get(arg0), "drawable", context.getPackageName()); loadBitmap(resId, img); return img; } public void loadBitmap(int resId, ImageView imageView) { if (cancelPotentialWork(resId, imageView)) { final BitmapWorkerTask task = new BitmapWorkerTask(imageView); imageView.setBackgroundResource(R.drawable.empty_photo); task.execute(resId); } } static class AsyncDrawable extends BitmapDrawable { private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { super(res, bitmap); bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>( bitmapWorkerTask); } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference.get(); } } public static boolean cancelPotentialWork(int data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final int bitmapData = bitmapWorkerTask.data; if (bitmapData != data) { // Cancel previous task bitmapWorkerTask.cancel(true); } else { // The same work is already in progress return false; } } // No task associated with the ImageView, or an existing task was // cancelled return true; } private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getBitmapWorkerTask(); } } return null; } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return (Bitmap) mMemoryCache.get(key); } class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { public int data = 0; private final WeakReference<ImageView> imageViewReference; public BitmapWorkerTask(ImageView imageView) { // Use a WeakReference to ensure the ImageView can be garbage // collected imageViewReference = new WeakReference<ImageView>(imageView); } // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; final Bitmap bitmap = decodeSampledBitmapFromResource( context.getResources(), data, 100, 100); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { if (imageViewReference != null && bitmap != null) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // Calculate ratios of height and width to requested height and // width final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // Choose the smallest ratio as inSampleSize value, this will // guarantee // a final image with both dimensions larger than or equal to the // requested height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } }
You can download the sample android source code from here.
Please leave your comments. I will be adding a modified version of this sample for using it to download from a URL and caching it in the disk and Memory in the coming posts.