Creating a basic Gallery in Phimpme Android

In the process of optimizing our Phimpme Android photo application, I had to change the implementation of the gallery in the application, as its loading time and smoothness is not satisfactory. How did I implement it?

In any gallery application, the primary necessity is having a details’ list of all available images on the device. For creating such a list, it is not necessary to iterate in all folders of the device storage for images. It is very time consuming task if we perform every time the app opens. Instead we can use database created by Android system for storing details of all available media on the device which can be accessed by any application. If we query the database with proper arguments, we get a cursor pointing to our target, using which we can form a list for our need.

Querying the mediastore for images can be implemented as shown below

Uri uri;
Cursor cursor;
int column_index;
String path = null,sortOrder;
ArrayList<String> imageList = new ArrayList<>();
uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.MediaColumns.DATA }; 
//DATA is the path to the corresponding image. We only need this for loading //image into a recyclerview

sortOrder = MediaStore.Images.ImageColumns.DATE_ADDED + “ DESC”;
//This sorts all images such that recent ones appear first

cursor = getContentResolver().query(uri, projection, null,null, sortOrder);

try {
       if (null != cursor) {
           column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
           while (cursor.moveToNext()) {
                 path = cursor.getString(column_index_data);
                 imageList.add(path);
            }
            cursor.close(); 
//imageList gets populated with paths to images by here
       }
}catch (Exception e){
       e.printStackTrace();
}

As we now have the list of paths to all images, we can proceed with displaying images using image file path. We can use RecyclerView for displaying grid of images as it is more optimized that default GridView. The whole process of displaying images can be summarized in following simple steps.

  1. Add RecyclerView widget to main layout and create a layout for gallery item.
  2. Create an adapter for populating RecyclerView.
  3. Initialize adapter with imageList and attach it to the recyclerView.

Setting Layout: 

Add the below code to the layout resource for your activity(activity_main.xml).

<android.support.v7.widget.RecyclerView
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/gallery_grid”/>

Inorder to use RecyclerView in the project, the following dependency for recyclerView has to be added in the build.gradle file

compile "com.android.support:recyclerview-v7:$supportLibVer"
//change supportLibVer to its corresponding value.

 

Now create a layout for item in the gallery. For this, you can create something like below.

gallery_item.xml

<?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="match_parent">
     <ImageView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:scaleType="centerCrop"
         android:id="@+id/image"/>
 </RelativeLayout>

Setting the Adapter:

Adapter populates the recyclerView with the list of resources given to it. For displaying images in RecyclerView, if we use normal method for setting image i.e imageview.setImageDrawable(xyz); the recyclerView would load extremely slow everytime we open the app. So, we can use an open-source image caching library Glide, for this purpose. It caches the images on its first load and lazy loads images into imageView giving a smooth experience. It can be used only if the following dependency is added to build.gradle.

compile 'com.github.bumptech.glide:glide:4.0.0-RC0'

Its sample implementation is also shown in the adapter code below.

OnItemClickListener() cannot be used with recyclerView directly, like gridView. So, a method for implementing such kind of listener is also shown in below implementation of adapter.

import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import java.io.File;
import java.util.ArrayList;

public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.mViewHolder> {

static ArrayList<String> galleryImageList;
private Context context;

public GalleryAdapter(ArrayList<String> imageList){
     galleryImageList = imageList;
}

public class mViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,View.OnLongClickListener {

     ImageView imageView;
     ItemClickListener itemClickListener;

     public mViewHolder(View view) {
         super(view);
         imageView = (ImageView)view.findViewById(R.id.image);
         view.setOnClickListener(this);
         view.setOnLongClickListener(this);
     }

     public void setClickListener(ItemClickListener itemClickListener) {
         this.itemClickListener = itemClickListener;
     }

     @Override
     public void onClick(View v) {
         itemClickListener.onClick(v, getPosition(), false);
     }

     @Override
     public boolean onLongClick(View v) {
         itemClickListener.onClick(v, getPosition(), true);
         return true;
     }
}

@Override
public GalleryAdapter.mViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     context = parent.getContext();
     View view = LayoutInflater.from(context).inflate(R.layout.gallery_item,parent,false);

//this gallery item is the one that we created above.

     DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

     RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(metrics.widthPixels/3,metrics.widthPixels/3);    //3 columns

     view.setLayoutParams(layoutParams);
     return new mViewHolder(view);
}

@Override
public void onBindViewHolder(GalleryAdapter.mViewHolder holder, final int position) {
     Glide.with(context)
             .load(Uri.fromFile(new File(galleryImageList.get(position)))
             //get path from list and covertin to URI
             .override(100,100) //final image will be this size(100x100)
             .thumbnail(0.1f)   //instead of empty placeholder, a                                              //thumbnail of 0.1th size of original image is used as //placeholder before complete loading of image
             .into(holder.imageView);
     holder.setClickListener(new ItemClickListener() {
         @Override
         public void onClick(View view,int position, boolean isLongClick) {
             if (isLongClick)
                 Toast.makeText(context, "Long Clicked " + position, Toast.LENGTH_SHORT).show();

             else
                 Toast.makeText(context, "Clicked " + position , Toast.LENGTH_SHORT).show();
         }
     });
}

@Override
public int getItemCount() {
     return (null != galleryImageList) ? galleryImageList.size() : 0;
}
}

With this, we reached the final step. 

Attaching the adapter to RecyclerView:

A grid layout manager with a required number of columns is made and attached to recyclerView.

The adapter is also initialized with the created Image List and is set to the recyclerView as shown below.

recyclerView = (RecyclerView)findViewById(R.id.gallery_grid);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(this,3);
// 3 columns
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setAdapter(new GalleryAdapter(createAndGetImageList()));

createAndGetImageList() is the first method discussed in post.