A 3D Carousel View for Android

android_carousel_gallery_view_01
Data controls take various forms, one of the mostly used being the listview. With the emerging “finger friendly” technologies, various controls have been developed to take advantage of easier data browsing. Android natively supports the Gallery view:
galleryview
But that looks a bit “unfinished” at least. It is, however, possible to extend the Gallery view, and handle the way each item is created and shown, as Neil Davies did, in his excellent CoverFlow Example tutorial.
I pushed things a bit further to offer the following:
– circular list: we’ll never get to the end, as the last item is linked to the first, as in a circular list. Browsing the items will let the content coming, continuously and regardless of the number of actual items.
– nice shadow effect: each item’s image is prolonged with a little shadow gradient at the bottom
– memory optimizations: we only create the shadow effect for the items in view, and not for the entire carousel structure, which can get very big. I had no issues in loading close to 2500 items in my view, and the animation, movement and memory status were all ok!
– scalable items, that will look ok , regardless of Android device screen size
– custom items, composed of an image and a text label
– filtering , as part of the attached carousel adapter structure, optimized for fast searches in large data sets
All in one a whole new concept that behaves with the same fluency regardless of the number of data items.

The code is composed of 4 major parts, each set in a separate class file.
1: CarouselView.java
It extends the Gallery class, to create our custom carousel view. In it’s constructor it exposes:

this.setStaticTransformationsEnabled(true);

that allows us to handle the transformation of each item displayed, based on it’s distance to the center: we want to rotate the items, as they get away from the center:

/**
 * {@inheritDoc}
 *
 * @see #setStaticTransformationsEnabled(boolean) 
 */ 
protected boolean getChildStaticTransformation(View child, Transformation t) {
	CarouselViewItem s = (CarouselViewItem)child;
	final int childCenter = s.getLeft() + s.getMaxW()/2;
	final int childWidth = s.getMaxW();
	
	int rotationAngle = 0;

	t.clear();
	t.setTransformationType(Transformation.TYPE_MATRIX);

	if (childCenter == mCoveflowCenter) {
		transformImageBitmap(s, t, 0);
	} else {      
		rotationAngle = (int) (((float) (mCoveflowCenter - childCenter)/ childWidth) *  mMaxRotationAngle);
		if (Math.abs(rotationAngle) > mMaxRotationAngle) 
			rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle : mMaxRotationAngle;   
		transformImageBitmap(s, t, rotationAngle);         
	}     
	return true;
}



 /**
  * Transform the Image Bitmap by the Angle passed 
  * 0 for center item
  * @param imageView ImageView the ImageView whose bitmap we want to rotate
  * @param t transformation 
  * @param rotationAngle the Angle by which to rotate the Bitmap
  */
 private void transformImageBitmap(CarouselViewItem s, Transformation t, int rotationAngle) {            
	 mCamera.save();
	 final Matrix imageMatrix = t.getMatrix();;
	 
	 final int imageWidth = s.getMaxW();
	 final int imageHeight = s.getMaxH();
	
	 final int rotation = Math.abs(rotationAngle);
	 //As the angle of the view gets less, zoom in     
	 float zoomAmount = (float) (rotation * 3);
	 mCamera.translate(0.0f, 0.0f, zoomAmount);          
	 
	 mCamera.rotateY(rotationAngle);
	 mCamera.getMatrix(imageMatrix);               
	 imageMatrix.preTranslate(-(imageWidth/2), -(imageHeight/2)); 
	 imageMatrix.postTranslate((imageWidth/2), (imageHeight/2));
	 mCamera.restore();
 }

2: CarouselViewItem.java
Is a class that defines one custom item, in this case a scalable image view with a text label at the bottom. Nothing fancy, just a class extending relativelayout with two views inside.

3: CarouselDataItem.java
The data we store for one item. We'll use this to populate our custom adapter, fill in data, and for further processing, eg. for filtering or for handling clicks on items.

4: CarouselViewAdapter.java
The custom view, as an extension to BaseAdapter. The getView method is essential: we get the item attached CarouselDataItem data, we use it to load the image from the disk, we transform the image by making a nice reflection effect and then create the corresponding CarouselViewItem object.
The advantage is that we do this on the fly, only for the displayed items. By doing so, we can show thousands of items in our Carousel view.
The reflection effect looks even better on the white-gray gradient set as background for the parent container.

public View getView(int position, View convertView, ViewGroup parent) {
	 // empty items
	 if (mDocus.size() ==0){
		 Bitmap empty = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.empty);
		 Bitmap reflection = createReflectedImage(empty);
		 empty.recycle();
		 CarouselViewItem scaIv = new CarouselViewItem(mContext, reflection, "empty doc", m_w, m_h);
		 return scaIv; //fix divide by zero on filtering
	 }
	 // we got content
	 if (position >= mDocus.size()) { 
		 position = position % mDocus.size(); 
	 } 
	 
	 Bitmap originalImage = BitmapFactory.decodeFile(mDocus.get(position).getImgPath());
	 Bitmap reflection = createReflectedImage(originalImage);
	 if (originalImage != null)
		 originalImage.recycle();
	 CarouselViewItem scaIv = new CarouselViewItem(mContext, reflection, mDocus.get(position).getDocText(), m_w, m_h);
 
	 return scaIv;
 } 

This class is also important for offering the filter option (the search box at the top). The idea here is that the adapter is the one that must identify the data we apply the sorting option to. In this case it is the text string attached to each item as the item name.

public Filter getFilter() {
	return new Filter() {

		@Override
		protected FilterResults performFiltering(CharSequence constraint) {
			final FilterResults oReturn = new FilterResults();
			//final CarouselDataItem[] mDocus;
			final ArrayList results = new ArrayList();
			if (mDocusOrig == null) mDocusOrig = mDocus;
			if (constraint != null) {
				if (mDocusOrig != null && mDocusOrig.size() > 0) {
					for (final CarouselDataItem g : mDocusOrig) {
						if (g.getDocText().toLowerCase()
								.contains(constraint.toString()))
							results.add(g);
					}
				}
				oReturn.values = results;
			}
			return oReturn;
		}

		@SuppressWarnings("unchecked")
		@Override
		protected void publishResults(CharSequence constraint,
				FilterResults results) {
			mDocus = (ArrayList) results.values;
			notifyDataSetChanged();
		}
	};
}

public void notifyDataSetChanged() {
	super.notifyDataSetChanged();
	//notifyChanged = true;
} 

Creating the Carousel view is easy. We need to create the control itself, and then populate it by attaching the custom adapter, filled with our data. This logic is done in MainActivity.java:

// copy images from assets to sdcard
AppUtils.AssetFileCopy(this, "/mnt/sdcard/plasma1.png", "plasma1.png", false);
AppUtils.AssetFileCopy(this, "/mnt/sdcard/plasma2.png", "plasma2.png", false);
AppUtils.AssetFileCopy(this, "/mnt/sdcard/plasma3.png", "plasma3.png", false);
AppUtils.AssetFileCopy(this, "/mnt/sdcard/plasma4.png", "plasma4.png", false);

//Create carousel view documents
ArrayList Docus = new ArrayList();
for (int i=0;i<1000;i++) {
	CarouselDataItem docu;
	if (i%4==0) docu = new CarouselDataItem("/mnt/sdcard/plasma1.png", 0, "First Image "+i);
	else if (i%4==1) docu = new CarouselDataItem("/mnt/sdcard/plasma2.png", 0, "Second Image "+i);
	else if (i%4==2) docu = new CarouselDataItem("/mnt/sdcard/plasma3.png", 0, "Third Image "+i);
	else docu = new CarouselDataItem("/mnt/sdcard/plasma4.png", 0, "4th Image "+i);
	Docus.add(docu);
}  

Creating the control itself:

// create the carousel
CarouselView coverFlow = new CarouselView(this);

// create adapter and specify device independent items size (scaling)
// for more details see: http://www.pocketmagic.net/2013/04/how-to-scale-an-android-ui-on-multiple-screens/
m_carouselAdapter =  new CarouselViewAdapter(this,Docus, m_Inst.Scale(400),m_Inst.Scale(300));
coverFlow.setAdapter(m_carouselAdapter);
coverFlow.setSpacing(-1*m_Inst.Scale(150));
coverFlow.setSelection(Integer.MAX_VALUE / 2, true);
coverFlow.setAnimationDuration(1000);
coverFlow.setOnItemSelectedListener((OnItemSelectedListener) this);

AppUtils.AddView(panel, coverFlow, LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT, 
		new int[][]{new int[]{RelativeLayout.CENTER_IN_PARENT}},
		-1, -1); 

The setOnItemSelectedListener will catch the click-on-items events. There's a toast message informing the user what was just clicked on:
android_carousel_gallery_view_02

The filter also works great, simply adding a text in the edittext at the top, will call the onTextChanged callback, and give the string to the custom adapter filter:

public void onTextChanged(CharSequence s, int start, int before, int count) {
	m_carouselAdapter.getFilter().filter(s.toString()); 
}  

The case of no results (after filtering is also handled), where a custom image is displayed, and the "empty doc" text as the label:
android_carousel_gallery_view_03
android_carousel_gallery_view_05

The code is released under GPL v2.0 license and can be downloaded below. For any questions feel free to use comments section.
Download source code here, or on Github.

This article has 47 Comments

  1. Hey pav1. You need to go to CarouselViewAdapter.java, and in public View getView(int position, View convertView, ViewGroup parent) to create a layout instead of the imageview and return it (instead of return scaIv). Easy.

  2. Hi, I need to stop circular functionality. if user is on the top most right position then first image should not show instead of user has to swipe in reverse.

  3. hello Radu, this is great sample.
    i’m android developer beginner, i need gridview at the bottom of the carousel.
    but i dont know how to add the gridview, in your code, it has texview “www.poketmagic.net”. i think its possible to add gridview at the same layout. please add more tutorial 😀
    thanks.

  4. Hi, I need to rotate this carousel automatically.I’m using this as my launching screen which I need to rotate it immediately while launching.Thanks for any help.

  5. RE:
    “Hey pav1. You need to go to CarouselViewAdapter.java, and in public View getView(int position, View convertView, ViewGroup parent) to create a layout instead of the imageview and return it (instead of return scaIv). Easy.”

    Where is the ImageView? And create what layout…

  6. hi Rojy

    you can download the images in a thread and populate the carousel when the download is complete

  7. Thanks Radu Motisan, its working

    just added a ImageLazyLoader in CarouselViewItem.java

    ImageLazyLoader imageLoader;
    imageLoader = new ImageLazyLoader(context);

    ScalableImageView scaIv = new ScalableImageView(context, w, h);
    scaIv.setId(100);
    scaIv.setImageBitmap(img);
    imageLoader.DisplayImage(“Url to download”, scaIv);

  8. I’m facing issues in Nexus 4 device (4.2.2). The selected Item image is skewed….
    The same code I checked it on Sony Xperia (4.2) it’s perfect.

    ThankYou.

  9. Hi stephen,

    I’ve used this on a dozen Android devices with no issues. You’ll probably need to debug it at your end and see what goes wrong on the Nexus 4.

  10. Hi Radu,
    I want to know what you have done to rotate images, I have implemented your code with some changes, it is working fine when I scrolls to right but the imageview before the first index does not show last index .what should I do?

  11. Hey is give me error

    [2014-02-12 14:41:50 – 3Dcarousel] Failed to install 3Dcoverflow.apk on device ‘3034FBAF8C8100EC’: Connection refused: connect
    [2014-02-12 14:41:50 – 3Dcarousel] java.net.ConnectException: Connection refused: connect
    [2014-02-12 14:41:50 – 3Dcarousel] Launch canceled!

  12. hi i’m android bediner. i want implement this code in my project please provide the link for downloading this code

  13. Hello,
    I want to track the ‘position’ value. From where it is being assigned?

  14. Also i am playing audio within the carousel, it is not playing sequentially.. The position values are 0,1,0…

  15. Hi Radu,

    I am using your 3D carousel for my Application and i am getting the Position value “arg3” and able to do them in order but i need to click on the images to go to a list view activity. For example i use 5 images and if i click on a image it should go to the list view activity respectively and i use JSON url for images and the new list view activity.

  16. Hi Radu,

    I am using your 3D carousel for my Application and i am getting the Position value “arg3” and able to do them in order but i need to click on the images to go to a list view activity. For example i use 5 images and if i click on a image it should go to the list view activity respectively and i use JSON url for images and the new list view activity.Below i have added the lines from your code which i am talking about.pls guide me.

    public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
    CarouselDataItem docu = (CarouselDataItem) m_carouselAdapter.getItem((int) arg3);
    if (docu!=null)
    Toast.makeText(this, “You’ve selected:”+docu.getDocText(), Toast.LENGTH_SHORT).show();
    }

    thank you.

  17. Nice tutorial but the problem i have is when i want to view it on a 4.0+ emulator, the images do not show.Also the section in the code where you wrote (copying images from assets folder to sdcard), it doesnt copy so i had to use the cmd and adb push command to push the images to the sdcard.

    Do you have any idea how i can make it work also on a 4.0+ emulator because it works perfectly on a 3.0+ emulator?

  18. i asked this question before but never got an answer. how do i make the sdcard copy the files from the assets folder because from your code, it is not working at all? i had to use the adb command on the cmd to push d images to the sdcard. saecondly, when i was done pshing the files into the sdcard, it only showed on the v3 of the emulator, the other versions didnt get the files. A little help here

  19. @Rohit Parmar: you can find the rotation logic in the source code. Just take a look.
    @Avi.c: hi Avi, what exactly do you want to rotate automatically?
    @Android Power: check your USB cable, disable then re-enable developer mode and usb debugging, etc. The problem is not related to this code.
    @sravani: the code is available both attached to this article, and also on google code.
    @Mubashir Meddekar: check the adapter class to see the position logic . I don’t quite understand the audio question
    @Vignesh: hi Vignesh, yes you can do this, simply create the second activity with the listview, then start it using an intent and startActivity when you click an item in the carousel. Currently there’s a toast message displayed when you click an item, just change that to what you want.
    @Sony: sorry no.
    @23Trivedi: you’re welcome! 🙂
    @James: you already good some good answers there, do you still need any help?
    @Andre: there are a few good approaches already documented on the internet, do a quick search, or let me know if you want me to google it for you.

  20. I’d appreciate that. i looked up some things on copying from the assets to the sdcard and what i saw was totally different from yours,like a total overhaul. please if you do know on how you can help me with that, i’d appreciate it. I’m new to this and your codes tutorial is great.

  21. hi
    this is pavan i am begainner in android i like u r application but i want when i click the image that time that image display the bigger image or occupie the full screen plz help me u give me full code of my requirement any one plz help me i need urgent.

  22. Hi Radu,

    Many thanks for your nice and clear tutorial. I got one question. Is there any way to change the selected image and text color? e.g. i want to use image1.png as default and when it is selected to use image1_selected.png

  23. Hi Radu.
    Thanks for this great code share,
    but I am having a problem,
    the Second image from center Image in the Right side images, it is overlapping the first one from center in right side views,
    I am not getting the way to solve this,
    Please help.

  24. Thanks for the code, looks good 🙂

    Have the same issue as Nirav – would like to know how to change it to not be circular, but have the images stop when user reaches the start/end.

  25. hi,
    thanks for ur sharing the code. i have a question how regarding to search, when i search one item it repeat several times,how i can show just one item when search that,no several times in screen.
    moreover, how i can stop carousel . i mean just like a gallery
    thanks
    peggy

  26. Hi thanks for this amazing carousel. One question I’m having, I can’t seem to have this as a secondary activity without it crashing my app. Is there something in the additional files I need to change? Basically I want to have a start screen and then click a button and have it start this carousel in another activity, but it crashes my app when attempt to start it, can you help me?

  27. @daniel, check the logs for what caused the crash. You probably didn’t include your second activity in the manifest xml.

  28. Omg that was it I cant believe I missed that, still getting used to the particulars with android 🙂 thanks a lot.

  29. @Sugumaran just call transformImageBitmap with angle 0, or comment the rotation function inside.

  30. @Radu Motisan, Thanks for your response.I need one more question to ask you.Center item click not working. Is there any method need to call that.
    Thanks ,

  31. @Radu Motisan, Is there any possible to change the orientation to vertical (i.e. vertical carousel view) ?

  32. @Radu Motisan, I have used below code to the adapter class getView method,Here When i set this listener,CarouselView scroll listener not working.
    scaIv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    Toast.makeText(mContext, “CLICK “+scaIv.getId(), Toast.LENGTH_SHORT).show();
    }
    });

  33. Cool tutorial but where are the sources can be found? How to implement method “createReflectedButmap” and so on?
    Could you please provide the sources?
    Thnx in advance.

  34. I need to select the current item and on that item, i want to intent to the next activity. onitemSelect doesn’t call on current item’s selection besides selecting the left or the right of the current item.

  35. http://stackoverflow.com/questions/6434912/android-how-to-give-click-event-on-gallery-item

    for those, who want to click on the current item (middle item) of the slider. follow this.

    on Main activity, do this

    coverFlow.setSpacing(-1 * m_Inst.Scale(150));
    coverFlow.setSelection(Integer.MAX_VALUE / 2, true);
    coverFlow.setAnimationDuration(1000);

    coverFlow.setOnItemClickListener((OnItemClickListener) this);
    AppUtils.AddView(panel, coverFlow, LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT,
    new int[][]{new int[]{RelativeLayout.CENTER_IN_PARENT}},
    -1, -1);
    }

    comment this line //coverFlow.setOnItemSelectedListener((OnItemSelectedListener) this);

Leave a Reply