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
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
FeatureCoverFlow
. So include it in activity layout (XML file) like this:
activity_main.xml
Like <?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>
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
Output when app running:<?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>
Programmatically code
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
As you can see above code, handling each item (cover) click event by this line:
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);
}
}
}
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
A POJO file, use as each item content:
Game.java
A layout for customizing 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;
}
}
Dialog
, only include a CardView
inside, show when click at each item:
dialog_game_info.xml
This VIDEO is result after running app in an Android real device:<?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>
References & Conclusions
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.