ViewPager
. It will not have a well interface but working better.
Creating a custom view
LinearLayout
and overriding onDraw()
to customizing scale animation:
CarouselLinearLayout.java
package info.devexchanges.carousellayout;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;
public class CarouselLinearLayout extends LinearLayout {
private float scale = CarouselPagerAdapter.BIG_SCALE;
public CarouselLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CarouselLinearLayout(Context context) {
super(context);
}
public void setScaleBoth(float scale) {
this.scale = scale;
this.invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// The main mechanism to display scale animation, you can customize it as your needs
int w = this.getWidth();
int h = this.getHeight();
canvas.scale(scale, scale, w/2, h/2);
}
}
Important Note
: You can custom from other ViewGroup
like RelativeLayout
, FrameLayout
,... based on your purpose.
Each carousel item is a
Fragment
, so it's layout must be a CarouselLinearLayout
as root like this:
fragment_image.xml
And in the programmatically code, scaling them to center of device screen by calculating the width and height:
<?xml version="1.0" encoding="utf-8"?>
<info.devexchanges.carousellayout.CarouselLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/pagerImg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:contentDescription="@string/app_name"
android:src="@drawable/image1" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</info.devexchanges.carousellayout.CarouselLinearLayout>
ItemFragment.java
package info.devexchanges.carousellayout;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ItemFragment extends Fragment {
private static final String POSITON = "position";
private static final String SCALE = "scale";
private static final String DRAWABLE_RESOURE = "resource";
private int screenWidth;
private int screenHeight;
private int[] imageArray = new int[]{R.drawable.image1, R.drawable.image2,
R.drawable.image3, R.drawable.image4, R.drawable.image5,
R.drawable.image6, R.drawable.image7, R.drawable.image8,
R.drawable.image9, R.drawable.image10};
public static Fragment newInstance(MainActivity context, int pos, float scale) {
Bundle b = new Bundle();
b.putInt(POSITON, pos);
b.putFloat(SCALE, scale);
return Fragment.instantiate(context, ItemFragment.class.getName(), b);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWidthAndHeight();
}
@SuppressLint("SetTextI18n")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (container == null) {
return null;
}
final int postion = this.getArguments().getInt(POSITON);
float scale = this.getArguments().getFloat(SCALE);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(screenWidth / 2, screenHeight / 2);
LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.fragment_image, container, false);
TextView textView = (TextView) linearLayout.findViewById(R.id.text);
CarouselLinearLayout root = (CarouselLinearLayout) linearLayout.findViewById(R.id.root_container);
ImageView imageView = (ImageView) linearLayout.findViewById(R.id.pagerImg);
textView.setText("Carousel item: " + postion);
imageView.setLayoutParams(layoutParams);
imageView.setImageResource(imageArray[postion]);
//handling click event
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), ImageDetailsActivity.class);
intent.putExtra(DRAWABLE_RESOURE, imageArray[postion]);
startActivity(intent);
}
});
root.setScaleBoth(scale);
return linearLayout;
}
/**
* Get device screen width and height
*/
private void getWidthAndHeight() {
DisplayMetrics displaymetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
screenHeight = displaymetrics.heightPixels;
screenWidth = displaymetrics.widthPixels;
}
}
Customizing ViewPager adapter
ViewPager
adapter, it's must implement OnPageChangeListener
interface and in onPageScrolled()
method (called when user scroll to change the "center item"), we must scaling current/next/back items size:
CarouselPagerAdapter.java
package info.devexchanges.carousellayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
public class CarouselPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener {
public final static float BIG_SCALE = 1.0f;
public final static float SMALL_SCALE = 0.7f;
public final static float DIFF_SCALE = BIG_SCALE - SMALL_SCALE;
private MainActivity context;
private FragmentManager fragmentManager;
private float scale;
public CarouselPagerAdapter(MainActivity context, FragmentManager fm) {
super(fm);
this.fragmentManager = fm;
this.context = context;
}
@Override
public Fragment getItem(int position) {
// make the first pager bigger than others
try {
if (position == MainActivity.FIRST_PAGE)
scale = BIG_SCALE;
else
scale = SMALL_SCALE;
position = position % MainActivity.count;
} catch (Exception e) {
e.printStackTrace();
}
return ItemFragment.newInstance(context, position, scale);
}
@Override
public int getCount() {
int count = 0;
try {
count = MainActivity.count * MainActivity.LOOPS;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return count;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
try {
if (positionOffset >= 0f && positionOffset <= 1f) {
CarouselLinearLayout cur = getRootView(position);
CarouselLinearLayout next = getRootView(position + 1);
cur.setScaleBoth(BIG_SCALE - DIFF_SCALE * positionOffset);
next.setScaleBoth(SMALL_SCALE + DIFF_SCALE * positionOffset);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
@SuppressWarnings("ConstantConditions")
private CarouselLinearLayout getRootView(int position) {
return (CarouselLinearLayout) fragmentManager.findFragmentByTag(this.getFragmentTag(position))
.getView().findViewById(R.id.root_container);
}
private String getFragmentTag(int position) {
return "android:switcher:" + context.pager.getId() + ":" + position;
}
}
Usage in Activity
ViewPager
object:
activity_main.xml
The most important work in the activity programmatically code is set margin between pages, I will pull them closer together to make a better interface:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="@+id/myviewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</LinearLayout>
MainActivity.java
At the last, by handling click event for each carousel item (you can see this code in package info.devexchanges.carousellayout;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
public class MainActivity extends AppCompatActivity {
public final static int LOOPS = 1000;
public CarouselPagerAdapter adapter;
public ViewPager pager;
public static int count = 10; //ViewPager items size
/**
* You shouldn't define first page = 0.
* Let define firstpage = 'number viewpager size' to make endless carousel
*/
public static int FIRST_PAGE = 10;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pager = (ViewPager) findViewById(R.id.myviewpager);
//set page margin between pages for viewpager
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int pageMargin = ((metrics.widthPixels / 4) * 2);
pager.setPageMargin(-pageMargin);
adapter = new CarouselPagerAdapter(this, getSupportFragmentManager());
pager.setAdapter(adapter);
adapter.notifyDataSetChanged();
pager.addOnPageChangeListener(adapter);
// Set current item to the middle page so we can fling to both
// directions left and right
pager.setCurrentItem(FIRST_PAGE);
pager.setOffscreenPageLimit(3);
}
}
ItemFragment
, we provide a destination activity to display the image which selected:
ImageDetailsActivity.java
And this is the destination activity layout:
package info.devexchanges.carousellayout;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
public class ImageDetailsActivity extends AppCompatActivity {
private ImageView imageView;
private Button button;
private static final String DRAWABLE_RESOURE = "resource";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_full_image);
imageView = (ImageView)findViewById(R.id.img);
button = (Button)findViewById(R.id.btnClose);
int drawbleResource = getIntent().getIntExtra(DRAWABLE_RESOURE, 0);
imageView.setImageResource(drawbleResource);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
@Override
public void onBackPressed() {
finish();
}
}
activity_full_image.xml
There are <?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:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter" />
<Button
android:id="@+id/btnClose"
android:padding="5dp"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="15dp"
android:text="Close" />
</RelativeLayout>
AndroidManifest.xml
and styles resource:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="info.devexchanges.carousellayout">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ImageDetailsActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"/>
</application>
</manifest>
styles.xml
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="Theme.AppCompat.Light.NoActionBar.FullScreen" parent="@style/Theme.AppCompat.Light">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
</resources>
Final thoughts
And in the landscape mode:
As you can see, by
ViewPager
widget, you can make a carousel layout easily. Although it not have a good interface (items are quite far with others) but it's is the official way to deal with this problem, hope it's helpful with your work. Finally, you can view my project on @Github.