Making Carousel Layout in Android


    Carousel Layout is a exciting topic in interface designing. It can act like a ViewPager with swiping to change content view, but also, the center item (selected one) is larger than another. This view style give a 3D effect, easy to manipulate with the elements.
    By this post, I provide a sample project which implements CoverFlow - a powerful library to make a flow (carousel) layout. Through it, I hope you can make your app design better!

Import library

    CoverFlow is pushed to Maven Central as a AAR file, so you just need to add the it's dependency to local build.gradle (usually locate in app module). Because of this library is build in the min-sdk is 15, so your project must set same as it:
build.gradle
apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
 
    defaultConfig {
        applicationId "info.devexchanges.carousellayout"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
 
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.github.moondroid.coverflow:library:1.0'
    compile 'com.android.support:cardview-v7:23.0.1'
}

Designing layout

    The class wich creating carousel layout in this library is FeatureCoverFlow. So include it in activity layout (XML file) like this:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:coverflow="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">
 
    <it.moondroid.coverflow.components.ui.containers.FeatureCoverFlow
        android:id="@+id/coverflow"
        coverflow:coverHeight="150dp"
        coverflow:coverWidth="100dp"
        coverflow:maxScaleFactor="1.5"
        coverflow:reflectionGap="0px"
        coverflow:rotationThreshold="0.5"
        coverflow:scalingThreshold="0.5"
        coverflow:spacing="0.6"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
    Like ListView or GridView, FeatureCoverFlow is subclass of AdapterView, it's also include some children views. So, make a layout for each item view (cover) by xml file:
item_flow_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="140dp"
    android:layout_height="180dp">
 
    <ImageView
        android:id="@+id/image"
        android:contentDescription="@string/app_name"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />
 
    <TextView
        android:id="@+id/name"
        android:layout_gravity="bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textAppearance="?android:attr/textAppearanceSmallInverse" />
 
</FrameLayout>
    Output when app running:

Programmatically code

    Like note above, FeatureCoverFlow need an adapter to store and show it's children view like ListView. So, our adapter class must implements BaseAdapter. It can be build like a normal ListView adapter, I also provide a ViewHolder class in it to make scroll smoother:
CoverFlowAdapter.java
package info.devexchanges.carousellayout;
 
import android.app.Dialog;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
 
import java.util.ArrayList;
 
public class CoverFlowAdapter extends BaseAdapter {
 
    private ArrayList<Game> data;
    private AppCompatActivity activity;
 
    public CoverFlowAdapter(AppCompatActivity context, ArrayList<Game> objects) {
        this.activity = context;
        this.data = objects;
    }
 
    @Override
    public int getCount() {
        return data.size();
    }
 
    @Override
    public Game getItem(int position) {
        return data.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
 
        ViewHolder viewHolder;
 
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.item_flow_view, null, false);
 
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
 
        viewHolder.gameImage.setImageResource(data.get(position).getImageSource());
        viewHolder.gameName.setText(data.get(position).getName());
 
        convertView.setOnClickListener(onClickListener(position));
 
        return convertView;
    }
 
    private View.OnClickListener onClickListener(final int position) {
        return new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                final Dialog dialog = new Dialog(activity);
                dialog.setContentView(R.layout.dialog_game_info);
                dialog.setCancelable(true); // dimiss when touching outside
                dialog.setTitle("Game Details");
 
                TextView text = (TextView) dialog.findViewById(R.id.name);
                text.setText(getItem(position).getName());
                ImageView image = (ImageView) dialog.findViewById(R.id.image);
                image.setImageResource(getItem(position).getImageSource());
 
                dialog.show();
            }
        };
    }
 
 
    private static class ViewHolder {
        private TextView gameName;
        private ImageView gameImage;
 
        public ViewHolder(View v) {
            gameImage = (ImageView) v.findViewById(R.id.image);
            gameName = (TextView) v.findViewById(R.id.name);
        }
    }
}
    As you can see above code, handling each item (cover) click event by this line:
convertView.setOnClickListener(onClickListener(position));
We should use this way instead of set coverFlow.setOnItemClickListener(AdapterView.OnItemClickListener clickListener()) in the activity code. In this project, I show a Dialog with a CardView to display each item (Game) details when it was clicked.
Note: to use CardView - a feature of Design Support Libary, you must add dependency to build.gradle like mine above.
    Back to activity code, you can handle scrolling event of this FeatureCoverFlow view (beside on click event was declared). Moreover, there is nothing special to do more:
MainActivity.java
package info.devexchanges.carousellayout;
 
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
 
import java.util.ArrayList;
 
import it.moondroid.coverflow.components.ui.containers.FeatureCoverFlow;
 
public class MainActivity extends AppCompatActivity {
 
    private FeatureCoverFlow coverFlow;
    private CoverFlowAdapter adapter;
    private ArrayList<Game> games;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        coverFlow = (FeatureCoverFlow) findViewById(R.id.coverflow);
 
        settingDummyData();
        adapter = new CoverFlowAdapter(this, games);
        coverFlow.setAdapter(adapter);
        coverFlow.setOnScrollPositionListener(onScrollListener());
    }
 
    private FeatureCoverFlow.OnScrollPositionListener onScrollListener() {
        return new FeatureCoverFlow.OnScrollPositionListener() {
            @Override
            public void onScrolledToPosition(int position) {
                Log.v("MainActiivty", "position: " + position);
            }
 
            @Override
            public void onScrolling() {
                Log.i("MainActivity", "scrolling");
            }
        };
    }
 
    private void settingDummyData() {
        games = new ArrayList<>();
        games.add(new Game(R.mipmap.assassins_creed, "Assassin Creed 3"));
        games.add(new Game(R.mipmap.avatar_3d, "Avatar 3D"));
        games.add(new Game(R.mipmap.call_of_duty_black_ops_3, "Call Of Duty Black Ops 3"));
        games.add(new Game(R.mipmap.dota_2, "DotA 2"));
        games.add(new Game(R.mipmap.halo_5, "Halo 5"));
        games.add(new Game(R.mipmap.left_4_dead_2, "Left 4 Dead 2"));
        games.add(new Game(R.mipmap.starcraft, "StarCraft"));
        games.add(new Game(R.mipmap.the_witcher_3, "The Witcher 3"));
        games.add(new Game(R.mipmap.tomb_raider, "Tom raider 3"));
        games.add(new Game(R.mipmap.need_for_speed_most_wanted, "Need for Speed Most Wanted"));
    }
}

Some necessary files

    Finally, I provide some other files to complete this project.
    A POJO file, use as each item content:
Game.java
package info.devexchanges.carousellayout;

public class Game {
    private String name;
    private int imageSource;

    public Game (int imageSource, String name) {
        this.name = name;
        this.imageSource = imageSource;
    }

    public String getName() {
        return name;
    }

    public int getImageSource() {
        return imageSource;
    }
}
    A layout for customizing Dialog, only include a CardView inside, show when click at each item:
dialog_game_info.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
 
        <ImageView
            android:id="@+id/image"
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:layout_gravity="center"
            android:contentDescription="@string/app_name"
            android:scaleType="centerCrop" />
 
        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textColor="@android:color/holo_green_dark" />
    </LinearLayout>
</android.support.v7.widget.CardView>
    This VIDEO is result after running app in an Android real  device:

References & Conclusions

    By this post, I hope you visualizing about Carousel layout and learned the way to build it in Android platform. More information with this libary, please see it's Github page, viewing it's source code for deep understanding.
    Images in this project code are taken from WallpagerWIDE.
    Let subscribe my blog for newest tutorials! Thank you!


Update: By reading user comments, I realized that this library is so weird, not behaving well with "dynamic data". So, by using ViewPager, you still can make a carousel layout very well, for more details, please reading my new post.

Share


Previous post
« Prev Post
Next post
Next Post »