Monday, October 7, 2013

GridView example: load images to GridView from SD Card in background

Recall from the "old exercise of GridView", loading images to GridView from SD Card in onCreate() method running in main thread. Actually, it may take long time to finish, so it should be moved to background thread. This exercise move the file loading operation to AsyncTask running in background thread.

Also introduce Reload button to demonstrate how to clear and reload the list.

Load images to GridView from SD Card


Here are some notes in my implementation:
  • In my trial experience, should not access ImageAdapter(extends BaseAdapter) from background thread such as doInBackground(). Doing so will conflict with ImageAdapter's getView() method, and generate IndexOutOfBoundsException occasionally. It whould be accessed in UI thread, so the clear(), add() and notifyDataSetChanged() have been moved to onPreExecute(), onProgressUpdate() and onPostExecute().
  • Cancel the AsyncTask if not need.
  • In my approach, always new another ImageAdapter when reloading; to prevent the list inside mixed with a invalid but still running AsyncTask.

package com.example.androidgridview;

import java.io.File;
import java.util.ArrayList;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   File[] files = targetDirector.listFiles();
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
   } else {
    imageView = (ImageView) convertView;
   }

   Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220,
     220);

   imageView.setImageBitmap(bm);
   return imageView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  public 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) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }

 }

 ImageAdapter myImageAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <Button
        android:id="@+id/reload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Reload"/>
    <GridView
        android:id="@+id/gridview"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:columnWidth="90dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>


download filesDownload the files.

download filesDownload and try the APK.



Next:
- getView() to load images in AsyncTask


12 comments:

Anonymous said...

hi, great post. Only thing I see wrong is that you aren't processing the bitmaps off the ui thread. If the directory you're reading from has a lot of images the approach used here will cause significant lag

Anonymous said...

Great post. Would you be able to tell me how i'd approach modifying the click listener to display a full size version of the thumbnail which was clicked?

fasal said...

Thanx for this code. i m facing one problem while scrolling, scrolling is not smooth any modification for it??

Erik said...

hello fasal shah,

maybe you have too many jobs in ui thread.

fasal said...

okay so i tested it with different size images what i found is scrolling is not working smoothly if folder contains my camera images but it does work fine with images having less size (randomly downloaded from net).. any help regarding camera images??

And please help with onclick listener to display full size image..
Thanks for concern..

Erik said...

hello fasal shah,

please check: http://android-er.blogspot.com/2014/09/getview-to-load-images-in-asynctask.html

Anonymous said...

Hi, I cant dispalay athe name of the image along with the image. When i use inaflator it stops working... Please HELP

Red pen said...

That's great ... but my gridView layout is not working properly ... Im trying to fix the problem ... but i dont get the problem ... could you help me with that .. Thanks :)

Anonymous said...

I need Code to display all images from sdcard using that same asynctask

Unknown said...

getting array null.. please help

Erik said...

hello Sunil Vishwakarma,

Have you added permission to read external storage?

Anonymous said...

Great job done. Thanks )