[TUT] Infinite Scrolling Gallery View

By blundell  

This is just something I put together when trying to figure out the best way to display my images to the user. One thing I did find out is the Android Gallery View sucks! I won’t be using it again for further projects but it does the job at the moment.

The issue with the android Gallery View is it doesn’t recycle it’s views. This means it is very heavy on garbage collection and when you create a custom view you can’t use the nice convertView object like you can with ListViews and others.

There is alternatives to this: EcoGallery However it’s pretty cumbersome, using reflection and you have to override a few classes. So as long as you haven’t got 50+ images in your Gallery I’d say your fine as it is. You could also merge the EcoGallery and this InfiniteGallery if you wish.

Therefore I have written this tutorial to show you how you can create a custom gallery that will allow you to swipe through your images in a continuous loop. Once you get to the end it will just start back at the beginning again without you even noticing.

Two InfiniteGallery custom views

I have written two implementations, one that will use images from your /res/drawable folder and one that will use your applications SD card cache. This tutorial has a minimum SDK of Android 2.2, you can move this down to 1.6 if you wish (just have to remove the FileManager class). There were two reasons for using 2.2, the long story is a whole new blog post, the short story is 1.6 is dead and SD card caching is 300% easier in >2.2.

As with all my tutorials the code is heavily commented to make it self explanatory, the source code is at the bottom of the post, so here we go:

Basic steps:

  • Create a custom Gallery that extends Gallery
  • Create a custom adapter to use in that gallery
  • Add your custom gallery to your activitys XML file
  • Get a reference to your custom gallery in your activity and pass it your images

Here is the custom gallery:

InfiniteGallery.java

package com.blundell.tut.ui.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.Gallery;

import com.blundell.tut.ui.adapter.InfiniteGalleryCachedAdapter;
import com.blundell.tut.ui.adapter.InfiniteGalleryResourceAdapter;

/**
 * This is the custom gallery view
 * To reference this from XML you need the package name followed by the class name
 * <i>ex: &lt;com.blundell.tut.ui.view.InfiniteGallery/&gt; </i>
 * 
 * @author Blundell
 */
public class InfiniteGallery extends Gallery {

	public InfiniteGallery(Context context) {
		super(context);
		init();
	}

	public InfiniteGallery(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public InfiniteGallery(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	private void init(){
		// These are just to make it look pretty
		setSpacing(10);
		setHorizontalFadingEdgeEnabled(false);
	}
	
	/**
	 * Set the InfiniteGallery to use images cached on the file system
	 * once this method is called the InfiniteGallery will set the adapter and draw it's images
	 * @param images an array of paths <i>ex: /mnt/blundell/cache/ic_launcher.png</i>
	 */
	public void setCachedImages(String[] images){
		setAdapter(new InfiniteGalleryCachedAdapter(getContext(), images));
		setSelection((getCount() / 2));
	}
	
	/**
	 * Set the InfiniteGallery to use images from your app resources
	 * once this method is called the InfiniteGallery will set the adapter and draw it's images
	 * @param images an array of ids <i>ex: R.drawable.ic_launcher</i>
	 */
	public void setResourceImages(int[] images){
		setAdapter(new InfiniteGalleryResourceAdapter(getContext(), images));
		setSelection((getCount() / 2));
	}
}

Here is the first adapter. At this point I am just giving you the raw files. If you take the time to download the source files, you may notice the organised package structure. This is taken from google open source examples and is best practice to keep an organised and clean flow to your source files, I highly recommend you follow it.

The first adapter uses images you place in your resources/drawables folder:

InfiniteGalleryResourceAdapter.java

package com.blundell.tut.ui.adapter;

import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.blundell.tut.util.AndroidUtils;

/**
 * This is our custom adapter for the Gallery
 * it will take a list of resource ids to use to draw it's children
 * it will allow the gallery to be scrolled in a continuous loop
 * @author Blundell
 */
public class InfiniteGalleryResourceAdapter extends BaseAdapter {
	/** The width of each child image */
	private static final int G_ITEM_WIDTH = 120;
	/** The height of each child image */
	private static final int G_ITEM_HEIGHT = 80;
	
	/** The context your gallery is running in (usually the activity) */
	private Context mContext;
	private int imageWidth;
	private int imageHeight;
	/** The array of resource ids to draw */
	private final int[] imageIds;

	public InfiniteGalleryResourceAdapter(Context c, int[] imageIds) {
		this.mContext = c;
		this.imageIds = imageIds;
	}

	/**
	 * The count of how many items are in this Adapter
	 * This will return the max number as we want it to scroll as much as possible
	 */
	@Override
	public int getCount() {
		return Integer.MAX_VALUE;
	}

	@Override
	public Object getItem(int position) {
		return position;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// convertView is always null in android.widget.Gallery

		ImageView i = getImageView();

		try {
			// first we calculate the item position in your list, because we have said the adapters size is Integer.MAX_VALUE
			// the position getView gives us is not use-able in its current form, we have to use the modulus operator
			// to work out what number in our 'array of paths' this actually equals
			int itemPos = (position % imageIds.length);

			i.setImageResource(imageIds[itemPos]);
			((BitmapDrawable) i.getDrawable()).setAntiAlias(true); // Make sure we set anti-aliasing otherwise we get jaggies (non-smooth lines)

		} catch (OutOfMemoryError e) {
			// a 'just in case' scenario
			Log.e("InfiniteGalleryResourceAdapter", "Out of memory creating imageview. Using empty view.", e);
		}

		return i;
	}

	/**
	 * Retrieve an ImageView to be used with the Gallery
	 * @return an ImageView with width and height set to DIP values
	 */
	private ImageView getImageView() {
		setImageDimensions();
		
		ImageView i = new ImageView(mContext);
		i.setLayoutParams(new Gallery.LayoutParams(imageWidth, imageHeight));
		i.setScaleType(ScaleType.CENTER_INSIDE);
		return i;
	}
	
	/**
	 * Sets the dimensions for each View that is used in the gallery
	 * lazily initialized so that we don't have to keep converting over and over
	 */
	private void setImageDimensions() {
		if (imageWidth == 0 || imageHeight == 0) {
			imageWidth = AndroidUtils.convertToPix(mContext, G_ITEM_WIDTH);
			imageHeight = AndroidUtils.convertToPix(mContext, G_ITEM_HEIGHT);
		}
	}
}

Here is the second adapter, this takes file paths so that you can show images that have been saved to the file system. For example “/mnt/dcim/blundell/lol.png”. It also caches these images so that your not constantly creating new drawables. This is used because as I explained at the top GalleryView doesn’t make use of convertView and so each view has to be recreated each time.

InfiniteGalleryCachedAdapter.java

package com.blundell.tut.ui.adapter;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.blundell.tut.util.AndroidUtils;

/**
 * This is our custom adapter for the Gallery
 * it will take a list of file paths to use to draw it's children
 * it will allow the gallery to be scrolled in a continuous loop
 * @author Blundell
 */
public class InfiniteGalleryCachedAdapter extends BaseAdapter {
	/** The width of each child image */
	private static final int G_ITEM_WIDTH = 120;
	/** The height of each child image */
	private static final int G_ITEM_HEIGHT = 80;
	
	/** The context your gallery is running in (usually the activity) */
	private Context mContext;
	private int imageWidth;
	private int imageHeight;
	/** The array of file paths to draw */
	private final String[] imagePaths;
	/** A crude cache of the drawables we have retrieved from the file system */
	private Drawable[] images;

	public InfiniteGalleryCachedAdapter(Context c, String[] imagePaths) {
		this.mContext = c;
		this.imagePaths = imagePaths;
		this.images = new Drawable[imagePaths.length];
	}
	
	/**
	 * The count of how many items are in this Adapter
	 * This will return the max number as we want it to scroll as much as possible
	 */
	@Override
	public int getCount() {
		return Integer.MAX_VALUE;
	}

	@Override
	public Object getItem(int position) {
		return position;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// convertView is always null in android.widget.Gallery
		
		// If we don't have an image path - return an empty view
		if(imagePaths.length == 0){
			return getImageView();
		}
		
		// first we calculate the item position in your list, because we have said the adapters size is Integer.MAX_VALUE
		// the position getView gives us is not use-able in its current form, we have to use the modulus operator
		// to work out what number in our 'array of paths' this actually equals
		// Once we have retrieved the Bitmap with decodeFile we will store it in a lazily initialized array
		// This means that each image will only be retrieved once from the file system and cached locally within the adapter
		// next time getView is called with a position that is in the images array it will quickly reference it
		int itemPos;
		try {
			itemPos = (position % imagePaths.length);
			if(images[itemPos] == null){
				String path = imagePaths[itemPos];
				
				BitmapDrawable drawable = new BitmapDrawable(BitmapFactory.decodeFile(path));
				drawable.setAntiAlias(true); // Make sure we set anti-aliasing otherwise we get jaggies (non-smooth lines)
				
				images[itemPos] = drawable;
			}
		} catch (OutOfMemoryError e) {
			// a 'just in case' scenario
			Log.e("InfiniteGalleryCachedAdapter", "Out of memory decoding image. Using empty view.", e);
			return getImageView();
		}
		
		ImageView i = getImageView();
		i.setImageDrawable(images[itemPos]);
		
		return i;
	}
	
	/**
	 * Retrieve an ImageView to be used with the Gallery
	 * @return an ImageView with width and height set to DIP values
	 */
	private ImageView getImageView() {
		setImageDimensions();
		
		ImageView i = new ImageView(mContext);
		i.setLayoutParams(new Gallery.LayoutParams(imageWidth, imageHeight));
		i.setScaleType(ScaleType.CENTER_INSIDE);
		return i;
	}
	
	/**
	 * Sets the dimensions for each View that is used in the gallery
	 * lazily initialized so that we don't have to keep converting over and over
	 */
	private void setImageDimensions() {
		if(imageWidth == 0 || imageHeight == 0){
			imageWidth = AndroidUtils.convertToPix(mContext, G_ITEM_WIDTH);
			imageHeight = AndroidUtils.convertToPix(mContext, G_ITEM_HEIGHT);
		}
	}
}

Now you have a custom gallery view with an adapter to ‘adapt’ your list of objects, you need to add it to your view hierarchy, i.e. add it to the activity layout. Here I have added two custom InfiniteGallery view’s so that I can show you both ways of loading the adapters (from resources / from file system) :

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="15dip"
        android:text="@string/hello" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="5dip"
        android:text="Gallery One - from /res/" />

    <com.blundell.tut.ui.view.InfiniteGallery
        android:id="@+id/galleryOne"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="15dip" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="5dip"
        android:text="Gallery Two - from /sdcard/" />

    <com.blundell.tut.ui.view.InfiniteGallery
        android:id="@+id/galleryTwo"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="15dip" />

</LinearLayout>

Once you have added the InfiniteGallery to your activity layout, you can reference it and set the array list of images you want to show. Here we instantiate both Gallerys with two different arrays. Don’t worry how the second list of files on the system is created, just know that it is referencing images on your SD card. If you do want to see how that works check out the FileCacheManager file in the source linked at the bottom of this post.

InfiniteScrollingGalleryActivity.java

package com.blundell.tut.ui.activity;

import android.app.Activity;
import android.os.Bundle;

import com.blundell.tut.R;
import com.blundell.tut.ui.view.InfiniteGallery;
import com.blundell.tut.util.FileCacheManager;

/**
 * A simple activity that holds two custom Gallery views, these are our custom
 * InfiniteGallery views that scroll infinitely when you swipe
 * 
 * @author Blundell
 */
public class InfiniteScrollingGalleryActivity extends Activity {
	
	/** An array of resource drawables to use in the gallery */
	int[] resourceImages = {
		R.drawable.ic_launcher,
		R.drawable.ic_launcher,
		R.drawable.ic_launcher,
		R.drawable.ic_launcher,
		R.drawable.ic_launcher,
		R.drawable.ic_launcher	
	};
	
	/** An array of path's to drawables we have cached to our SD card */
	String[] cachedOnSDImages = new String[6];
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set the layout to use
        setContentView(R.layout.activity_main);
        
        // This is just a helper method to initilise some images onto the SD card
        // in your real application this is up to you
        saveSomeFilesToSDCard();
        
        // Get a handle to the first gallery in our XML layout and
        // set the images to draw, this example draws images from /res/drawable
        InfiniteGallery galleryOne = (InfiniteGallery) findViewById(R.id.galleryOne);
        galleryOne.setResourceImages(resourceImages);
        
        // Get a handle to the second gallery in our XML layout and
        // set the images to draw, this example draws images from a list of image paths
        // that in this example are on the SD card in your applications private cache folder
        InfiniteGallery galleryTwo = (InfiniteGallery) findViewById(R.id.galleryTwo);
        galleryTwo.setCachedImages(cachedOnSDImages);
    }

    /**
     * You can just ignore this for the TUT, it's just a helper method to place 
     * some images onto the SD card an retrieve their path
     */
	private void saveSomeFilesToSDCard() {
		FileCacheManager manager = new FileCacheManager(this);
		String path = manager.saveImage(R.drawable.ic_launcher);
		for (int i = 0; i < cachedOnSDImages.length; i++) {
			cachedOnSDImages[i] = path;
		}
	}
}

Finally to make use of the SD card caching we need to add the SD card permission into the manifest:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blundell.tut"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".ui.activity.InfiniteScrollingGalleryActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Here is the whole project source files, hope you enjoyed it and please comment!

—-> InfiniteScrollingGalleryTut Project Source Files <----




25 Comments

  1. Ken Antares
    Posted July 11, 2013 at 2:28 am | Permalink | Reply

    Cannot find “AndroidUtils” you imported in InfiniteGalleryCachedAdapter.java, could you post the file “AndroidUtils” please?

  2. zen kun
    Posted October 24, 2012 at 10:14 pm | Permalink | Reply

    oh so shame i cant get fastscrolling, its anyway to asynload images from the web or the sdcard?

  3. zen kun
    Posted October 23, 2012 at 11:23 pm | Permalink | Reply

    Awesome i made some modifications but wow thanks iw as looking something like this :) 2 questions its any way to get a “fastScrolling” like ListView to move between items :D thanks

    • blundell
      Posted October 24, 2012 at 6:32 pm | Permalink | Reply

      Glad you like. Nope sorry and with the Gallery class being deprecated if you want to change the functionality I would recommend checking out the

  4. Abdul Mateen
    Posted July 17, 2012 at 8:34 am | Permalink | Reply

    Its nice, but I need to show gallery as 3D circle.

  5. vahid
    Posted April 18, 2012 at 11:39 am | Permalink | Reply

    how to scroll automatically ? using your component

    • blundell
      Posted April 19, 2012 at 6:38 pm | Permalink | Reply

      Just add a Timer that changes the position.

      • Gaz
        Posted July 2, 2013 at 7:54 am | Permalink | Reply

        How can you do this? ive been trying for hours, please give a proper explanation do we call getView ourself??

        • blundell
          Posted July 2, 2013 at 8:51 pm | Permalink | Reply

          What do you mean? The source code is linked at the bottom of the post if you want to take a look

  6. frozenhill
    Posted March 19, 2012 at 9:30 pm | Permalink | Reply

    hi man
    nice tutorial
    can you help me in setting a title for each image ( text to descripe each image) please

    • blundell
      Posted March 20, 2012 at 8:14 am | Permalink | Reply

      Hi,

      You could wrap the GalleryView in a LinearLayout and have a TextView for your title, add an ItemChangedListener and in this method update the TextView. Hope that helps.

      • frozenhill
        Posted March 20, 2012 at 12:45 pm | Permalink | Reply

        thanks my friend for your replay , im new in android development , would you please , explain more with sample code ,
        appreciate it
        thanks

        • blundell
          Posted March 21, 2012 at 8:25 am | Permalink | Reply

          Have a go and post when you get stuck on Stackoverflow, I can always help you there.

          • frozenhill
            Posted March 21, 2012 at 9:50 am | Permalink

            i already post it in stackoverflow now , please would y please check it and help me in solve it , its under title :
            ( Infinite Scrolling Gallery View with text description ) in stackoverflow site ,thanks

          • frozenhill
            Posted March 21, 2012 at 10:10 pm | Permalink

            thanks alot for your help , im sorry to say that i dont know how to apply the above code to your tutorial , its the matter of me , coz im beginner in development , would you please explain and edit more ,thanks , i post that in STACKOVERFLOE SITE ALSO

          • frozenhill
            Posted March 22, 2012 at 9:23 am | Permalink

            ok my friend i will keep trying to perform that ,i hope i can do it
            really thanks

    • frozenhill
      Posted April 5, 2012 at 6:14 am | Permalink | Reply

      Hi my dear friend
      would you please check my update question in stackoverflow please under post name:

      (infinite scrolling gallery view with text descriptin)
      please
      thanks , appreciated your help .

  7. Posted March 8, 2012 at 12:38 pm | Permalink | Reply

    This is very nice and help full. thank you very much for your time.

  8. Resul
    Posted February 10, 2012 at 4:42 pm | Permalink | Reply

    This tut is reallly reallly coool.

    Triple +1 for you.

  9. racumin
    Posted February 10, 2012 at 10:35 am | Permalink | Reply

    Will this work if you swipe it backwards? I tried the same approach

    getCount = Integer.MAX_VALUE

    and

    position % size

    But when I scrolled the 1st time backwards, it wont go to the last image from the list

    • blundell
      Posted February 10, 2012 at 8:23 pm | Permalink | Reply

      I believe you forgot to set the start position of the spinner to the middle. After you set the adapter: setSelection(getCount() / 2);

      • racumin
        Posted February 13, 2012 at 11:00 am | Permalink | Reply

        I forgot that. That did the work. Thanks!

  10. Posted February 9, 2012 at 11:13 am | Permalink | Reply

    Awesome example.
    Keep doing good work.

    Thanx :)

3 Trackbacks

  1. [...] Infinite Scrolling Gallery View By blundell [...]

  2. By OutOfMemory error - do you use runnables? | Blundell on February 16, 2012 at 1:41 pm

    [...] code above creates a new adapter for your gallery, by the way this is ripped out my Infinite Scrolling Gallery. [...]

Post a Comment

Your email is never shared. Required fields are marked *

*
*