In most of our applications we tend to need to display a series of images. Images make our applications look more modern and interactive and they are a great way to pass information visually.
Sometimes you may even need to show your item lists horizontally. One way is to use a recyclerview with horizontal orientation. However in this case the items won't be auto-flipped or faded.
Thus we will first look at how to create a custom carousel based on recyclerview, the we will curate some of the best libraries and examples already written by other developers and available for re-use. You could always customize them since they are all open source.
Most of them are actually based on recyclerview and some do need a custom adapter. This gives them flexibility and power.
In this article you will learn:
- How to create a custom image carousel in android based on recyclerview.
- How to use third party libraries to implement a carousel.
We use the following languages:
- Kotlin
- Java
NB/= This article is broken down into several pages. Each page teaches a unique concept. Here are the taught concepts: Page 1: Implement a custom carousel Page 2: Use libraries to implement a carousel
Concept 1: Creating a Custom Carousel
Example 1: Kotlin android Horizontal Carousel
How to implement a custom horizontal image carousel using recycelrview in Kotlin.
Step 1: Create Project
Start by creating an empty Android Studio
Kotlin project.
Step 2: Dependencies
No third party libraries are needed for this project.
Step 3: Create a Custom Carousel View
We will create a custom carousel view by extending the recyclerview:
HorizontalCarouselRecyclerView.kt
package supahsoftware.androidexamplecarousel
import android.animation.ArgbEvaluator
import android.content.Context
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class HorizontalCarouselRecyclerView(
context: Context,
attrs: AttributeSet
) : RecyclerView(context, attrs) {
private val activeColor by lazy { ContextCompat.getColor(context, R.color.blue) }
private val inactiveColor by lazy { ContextCompat.getColor(context, R.color.gray) }
private var viewsToChangeColor: List<Int> = listOf()
fun <T : ViewHolder> initialize(newAdapter: Adapter<T>) {
layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
newAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
post {
val sidePadding = (width / 2) - (getChildAt(0).width / 2)
setPadding(sidePadding, 0, sidePadding, 0)
scrollToPosition(0)
addOnScrollListener(object : OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollChanged()
}
})
}
}
})
adapter = newAdapter
}
fun setViewsToChangeColor(viewIds: List<Int>) {
viewsToChangeColor = viewIds
}
private fun onScrollChanged() {
post {
(0 until childCount).forEach { position ->
val child = getChildAt(position)
val childCenterX = (child.left + child.right) / 2
val scaleValue = getGaussianScale(childCenterX, 1f, 1f, 150.toDouble())
child.scaleX = scaleValue
child.scaleY = scaleValue
colorView(child, scaleValue)
}
}
}
private fun colorView(child: View, scaleValue: Float) {
val saturationPercent = (scaleValue - 1) / 1f
val alphaPercent = scaleValue / 2f
val matrix = ColorMatrix()
matrix.setSaturation(saturationPercent)
viewsToChangeColor.forEach { viewId ->
val viewToChangeColor = child.findViewById<View>(viewId)
when (viewToChangeColor) {
is ImageView -> {
viewToChangeColor.colorFilter = ColorMatrixColorFilter(matrix)
viewToChangeColor.imageAlpha = (255 * alphaPercent).toInt()
}
is TextView -> {
val textColor = ArgbEvaluator().evaluate(saturationPercent, inactiveColor, activeColor) as Int
viewToChangeColor.setTextColor(textColor)
}
}
}
}
private fun getGaussianScale(
childCenterX: Int,
minScaleOffest: Float,
scaleFactor: Float,
spreadFactor: Double
): Float {
val recyclerCenterX = (left + right) / 2
return (Math.pow(
Math.E,
-Math.pow(childCenterX - recyclerCenterX.toDouble(), 2.toDouble()) / (2 * Math.pow(
spreadFactor,
2.toDouble()
))
) * scaleFactor + minScaleOffest).toFloat()
}
}
Step 4: Design Layouts
We will need two layouts for this project:
(a). list_item.xml
A layout to represent a single carousel item:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:paddingStart="24dp"
android:paddingTop="64dp"
android:paddingEnd="24dp"
android:paddingBottom="64dp">
<ImageView
android:id="@+id/list_item_background"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:src="@drawable/icon_background_blue" />
<ImageView
android:id="@+id/list_item_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/list_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/list_item_background"
android:layout_centerHorizontal="true"
android:layout_marginTop="32dp"
android:textColor="@color/blue"
android:textSize="22sp" />
</RelativeLayout>
(b). activity_main.xml
Will contain our custom Horizontal Carousel recyclerview:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">
<supahsoftware.androidexamplecarousel.HorizontalCarouselRecyclerView
android:id="@+id/item_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:clipToPadding="false"
android:overScrollMode="never" />
</LinearLayout>
Step 5: Create an Adapter
Create our carousel adapter:
ItemAdapter.kt
class ItemAdapter(val itemClick: (position:Int,item:Item) -> Unit) : RecyclerView.Adapter<ItemViewHolder>() {
private var items: List<Item> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder =
ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false))
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(items[position])
holder.itemView.setOnClickListener {
itemClick(position,items[position])
}
}
override fun getItemCount() = items.size
fun setItems(newItems: List<Item>) {
items = newItems
notifyDataSetChanged()
}
}
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Item) {
view.list_item_text.text = "${item.title}"
view.list_item_icon.setImageResource(item.icon)
}
}
Step 6: Create MainActivity
Here is the code for MainActivity:
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val itemAdapter by lazy {
ItemAdapter { position: Int, item: Item ->
Toast.makeText(this@MainActivity, "Pos ${position}", Toast.LENGTH_LONG).show()
item_list.smoothScrollToPosition(position)
} }
private val possibleItems = listOf(
Item("Airplanes", R.drawable.ic_airplane),
Item("Cars", R.drawable.ic_car),
Item("Food", R.drawable.ic_food),
Item("Gas", R.drawable.ic_gas),
Item("Home", R.drawable.ic_home)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
item_list.initialize(itemAdapter)
item_list.setViewsToChangeColor(listOf(R.id.list_item_background, R.id.list_item_text))
itemAdapter.setItems(getLargeListOfItems())
}
private fun getLargeListOfItems(): List<Item> {
val items = mutableListOf<Item>()
(0..40).map { items.add(possibleItems.random()) }
return items
}
}
data class Item(
val title: String,
@DrawableRes val icon: Int
)
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
3. | Code: Apache 2.0 License |
Concepts 2: Best Android Image Carousel Libraries - 2021
Proceed to the next page to view the best android image carousel libraries this year.
Concepts 2: Best Android Image Carousel Libraries - 2021
(a). Pluto
Pluto is an Easy, lightweight and high performance slider view library for Android! You can customize it to any view since it based RecyclerView.
The differnce of this library, we are not following the concept of images model. Pluto is not depending on any Image loading library.
Here is the demo:
Step 1: How to Install Pluto
Pluto requires API 17+.
This library is available in jCenter which is the default Maven repository used in Android Studio. You can also import this library from source as a module.
Step 2: How to Use pluto
To create an image carousel using Pluto, first create your own adapter extending the PlutoAdapter
:
Kotlin
class YourAdapter(items: MutableList<YourModel> , onItemClickListener: OnItemClickListener<Movie>? = null)
: PlutoAdapter<YourModel, YourViewHolder>(items,onItemClickListener) {
override fun getViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(parent, R.layout.item_your_layout)
}
class YourViewHolder(parent: ViewGroup, itemLayoutId: Int) : PlutoViewHolder<YourModel>(parent, itemLayoutId) {
private var ivPoster: ImageView = getView(R.id.iv_poster)
private var tvRating: TextView = getView(R.id.tv_rating)
override fun set(item: YourModel, position: Int) {
// yourImageLoader.with(mContext).load(item.getPosterId()).into(ivPoster);
tvRating.text = item.imdbRating
}
}
}
Java
public class YourAdapter extends PlutoAdapter<YourModel, YourViewHolder> {
public YourAdapter(List<YourModel> items) {
super(items);
}
@Override
public ViewHolder getViewHolder(ViewGroup parent, int viewType) {
return new YourViewHolder(parent, R.layout.item_your_layout);
}
public static class YourViewHolder extends PlutoViewHolder<YourModel> {
ImageView ivPoster;
TextView tvRating;
public YourViewHolder(ViewGroup parent, int itemLayoutId) {
super(parent, itemLayoutId);
ivPoster = getView(R.id.iv_poster);
tvRating = getView(R.id.tv_rating);
}
@Override
public void set(YourModel item, int pos) {
// yourImageLoader.with(mContext).load(item.getPosterId()).into(ivPoster);
tvRating.setText(item.getImdbRating());
}
}
}
Then add PlutoView in your XML layouts:
<com.opensooq.pluto.PlutoView
android:id="@+id/slider_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:indicator_visibility="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
Then finaly attach the adapter to Pluto:
Kotlin
val pluto = findViewById<PlutoView>(R.id.slider_view)
val yourAdapter = YourAdapter(yourModelList, object : OnItemClickListener<Movie> {
override fun onItemClicked(item: yourModel?, position: Int) {
}
})
pluto.create(adapter, lifecycle = lifecycle)//pass the lifecycle to make the slider aware of lifecycle to avoid memory leak and handling the pause/destroy/resume
Java
PlutoView pluto = findViewById(R.id.slider_view);
YourAdapter adapter = new YourAdapter(yourModelsList);
pluto.create(adapter,getLifecycle()); //pass the lifecycle to make the slider aware of lifecycle to avoid memory leak and handling the pause/destroy/resume
Method | usage |
---|---|
create(PlutoAdapter adapter, long duration,Lifecycle lifecyle) |
it is the initialization method it take your adapter and the duration of waiting between each slide and lifecycle to make slider lifecylce-aware |
startAutoCycle() |
by default the value of autocycle is true, it determine to start autocycling or not |
stopAutoCycle() |
it stops the auto cycling of the view |
moveNextPosition() |
if you are the auto cylce is off you can manually move next with this method |
movePrevPosition() |
if you are the auto cylce is off you can manually move to the previus item with this method |
setIndicatorPosition(IndicatorPosition presetIndicator) |
to set the position of the indicator where values are CENTER_BOTTOM RIGHT_BOTTOM LEFT_BOTTOM CENTER_TOP RIGHT_TOP LEFT_TOP |
setCustomIndicator(PlutoIndicator indicator) |
if you want to custom the indicator use this method for custom indicators check Custom indicators |
Example
Let's look at a full example of how to create an image carousel using Pluto:
SliderAdapter
Here is a custom adapter for the image slider:
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide
import com.opensooq.pluto.base.PlutoAdapter
import com.opensooq.pluto.base.PlutoViewHolder
import com.opensooq.pluto.listeners.OnItemClickListener
class SliderAdapter(items: MutableList, onItemClickListener: OnItemClickListener) : PlutoAdapter<Movie, SliderAdapter.ViewHolder>(items, onItemClickListener) {
override fun getViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(parent, R.layout.item_movie_promotion)
}
class ViewHolder(parent: ViewGroup, itemLayoutId: Int) : PlutoViewHolder(parent, itemLayoutId) {
private var ivPoster: ImageView = getView(R.id.iv_poster)
private var tvRating: TextView = getView(R.id.tv_rating)
override fun set(item: Movie, position: Int) {
Glide.with(context).load(item.posterId).into(ivPoster)
tvRating.text = item.imdbRating
}
}
}
MainActivity.
The main activity:
package com.opensooq.plutodemo
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.widget.LinearLayout
import com.opensooq.pluto.PlutoView
import com.opensooq.pluto.base.PlutoAdapter
import com.opensooq.pluto.listeners.OnItemClickListener
import com.opensooq.pluto.listeners.OnSlideChangeListener
const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
private fun getAvenger(): MutableList {
val items = mutableListOf()
items.add(Movie("7.1", R.drawable.ic_captain_marvel))
items.add(Movie("9.2", R.drawable.ic_end_game))
items.add(Movie("7.5", R.drawable.ic_dr_strange))
items.add(Movie("7.9", R.drawable.ic_iron_man))
return items
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val pluto = findViewById(R.id.slider_view)
val adapter = SliderAdapter(getAvenger(), object : OnItemClickListener {
override fun onItemClicked(item: Movie?, position: Int) {
Log.d(TAG, "on Item clicked $position ${item?.imdbRating}")
}
})
pluto.create(adapter, lifecycle = lifecycle)
pluto.setOnSlideChangeListener(object : OnSlideChangeListener {
override fun onSlideChange(adapter: PlutoAdapter<\*, \*>, position: Int) {
Log.d(TAG, "on slide change $position ")
}
})
val button = findViewById(R.id.indicator_example)
button.setOnClickListener {
startActivity(Intent(this, CustomIndicatorActivity::class.java))
}
}
}
Find source code here.
Reference
find more documentation and code reference here.
(b). Sliding-Carousel
Android Library for easing Slider insertion to your apps with support for Images.
Here are it's features:
- Written in Kotlin
- No boilerplate code
- Easy initialization
Here's the demo:
Step 1: Install Sliding Carousel
Specify the jitpakc as repository in your allProjects closure in the root level build.gardle file:
Then add this implementation statement in the dependencies closure in the app level build.gradle:
Now sync your project.
Step 2: Add Carousel to Layout
Now add the sliding carousel to your xml layout:
<com.limerse.slider.ImageCarousel
android:id="@+id/carousel"
android:layout_width="match_parent"
android:layout_height="256dp" />
Step 3: Write Code
Reference the ImageCarousel:
Register it to the lifecycle of the activity:
Create a list to hold carousel items:
Now add images with or without captions:
// Image URL with caption
list.add(
CarouselItem(
imageUrl = "https://images.unsplash.com/photo-1532581291347-9c39cf10a73c?w=1080",
caption = "Photo by Aaron Wu on Unsplash"
)
)
Image without caption:
// Just image URL
list.add(
CarouselItem(
imageUrl = "https://images.unsplash.com/photo-1534447677768-be436bb09401?w=1080"
)
)
To add image url with header:
// Image URL with header
val headers = mutableMapOf<String, String>()
headers["header_key"] = "header_value"
list.add(
CarouselItem(
imageUrl = "https://images.unsplash.com/photo-1534447677768-be436bb09401?w=1080",
headers = headers
)
)
Image Carousel Attributes
Here are all attributes of this library:
<com.limerse.slider.ImageCarousel
android:id="@+id/carousel"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:showTopShadow="true"
app:topShadowAlpha="0.6"
app:topShadowHeight="32dp"
app:showBottomShadow="true"
app:bottomShadowAlpha="0.6"
app:bottomShadowHeight="64dp"
app:showCaption="true"
app:captionMargin="0dp"
app:captionTextSize="14sp"
app:showIndicator="true"
app:indicatorMargin="0dp"
app:imageScaleType="centerCrop"
app:carouselBackground="#00000000"
app:imagePlaceholder="@drawable/ic_picture"
app:carouselPadding="0dp"
app:carouselPaddingBottom="0dp"
app:carouselPaddingEnd="0dp"
app:carouselPaddingStart="0dp"
app:carouselPaddingTop="0dp"
app:showNavigationButtons="true"
app:previousButtonLayout="@layout/previous_button_layout"
app:previousButtonId="@id/btn_previous"
app:previousButtonMargin="4dp"
app:nextButtonLayout="@layout/next_button_layout"
app:nextButtonId="@id/btn_next"
app:nextButtonMargin="4dp"
app:carouselType="BLOCK"
app:carouselGravity="CENTER"
app:scaleOnScroll="false"
app:scalingFactor="0.15"
app:autoWidthFixing="true"
app:autoPlay="false"
app:autoPlayDelay="3000"
app:infiniteCarousel="true"
app:touchToPause="true" />
Example
You can find a full example here.
Reference
Find more here