Card Swipe View with Indicator using View Pager

asif ali
Level Up Coding
Published in
4 min readJan 29, 2020

--

In this article, I am going to show you how to implement a dynamic swipeable card view with indicators below using view pager in Kotlin. I assume you have some basic knowledge in Kotlin and Android

We will now head to create a new empty project and without further ado let’s dive right into the code:

Step 1:

First, we will set the activity_main.xml layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.PetsFragment">


<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_empty"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/gotham_bold"
android:text="@string/txt_pets"
android:textColor="@color/txt_dark_gray"
android:textSize="26sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_empty"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/img_add_pets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add"
android:scaleType="center"
app:layout_constraintBottom_toTopOf="@+id/textView6"
app:layout_constraintEnd_toEndOf="@+id/textView6"
app:layout_constraintStart_toStartOf="@+id/textView6" />

<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/add_first_pet"
android:fontFamily="@font/gotham_book"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>


<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent">


<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:fontFamily="@font/gotham_bold"
android:text="@string/txt_pets"
android:textColor="@color/txt_dark_gray"
android:textSize="26sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_add"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="@+id/textView7"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textView7" />

<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="50dp"
android:clipToPadding="false"
android:foregroundGravity="center"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView7" />

<LinearLayout
android:id="@+id/slider_dots"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:gravity="center_vertical|center_horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view_pager" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 2:

Open MainActivity.kt and declared these variables as mentioned below:

private lateinit var adapter: PetAdapter
private lateinit var models: ArrayList<PetsModel>
private lateinit var viewPager: ViewPager
var sliderDotspanel: LinearLayout? = null
private var dotscount = 0

Now its time to initialize the variables and I’m going to add dummy values to the list that will be rendered on MainActivity:

viewPager = findViewById(R.id.view_pager)
sliderDotspanel = findViewById(R.id.slider_dots)

models = ArrayList()
models.add(PetsModel(R.drawable.dummy_image,"Dobby", "Dog"))
models.add(PetsModel(R.drawable.dummy_image,"Kitto", "Cat"))
models.add(PetsModel(R.drawable.dummy_image,"Cozmo", "Lambardor"))
models.add(PetsModel(R.drawable.dummy_image,"Tiger", "German Shepherd"))
models.add(PetsModel(R.drawable.dummy_image,"Husky", "Husky"))
models.add(PetsModel(R.drawable.dummy_image,"Cat", "Unknown"))
adapter = PetAdapter(models, this@MainActivity)
viewPager.adapter = adapter

Step 3:

Let’s move to the adapter section and see how we can create something called a pageAdapter which is responsible for rendering the fragments on the screen. Create a new Java class named “PetAdapter” and extend “PagerAdapter” Simply copy-paste the following code inside the class and I’ll explain it in a simple manner.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;

import com.celeritassolution.cardwipeview.R;
import com.celeritassolution.cardwipeview.model.PetsModel;

import java.util.ArrayList;

public class PetAdapter extends PagerAdapter {

private ArrayList<PetsModel> models;
private LayoutInflater layoutInflater;
private Context context;

public PetAdapter(ArrayList<PetsModel> models, Context context) {
this.models = models;
this.context = context;
}

@Override
public int getCount() {
return models.size();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view.equals(object);
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, final int position) {
layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.item, container, false);

ImageView imageView;
TextView title, desc;

imageView = view.findViewById(R.id.image);
title = view.findViewById(R.id.title);
desc = view.findViewById(R.id.txt_des);


imageView.setImageResource(models.get(position).getImage());
title.setText(models.get(position).getTitle());
desc.setText(models.get(position).getDesc());

container.addView(view, 0);
return view;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View)object);
}
}

We first define the constructor for the class and we will pass two parameters an array list which will hold data to decide how many items will be created on main activity and a context. We are more concerned with the instantiateItem() which will do the magic for us. Basically this method will create the page for the given position. The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup)

Ref:https://developer.android.com/reference/android/support/v4/view/PagerAdapter

and the getCount() method that returns the number of views.

Step 4:

Let’s create item.xml layout that will be inflated in the adapter:

The layout for the item could be anything, for simplicity we will go this layout or you can have your own implementation for the layout as well. :)
So just copy and paste the code down the line.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
app:cardCornerRadius="8dp">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:cropToPadding="true"
android:scaleType="centerCrop"
android:src="@drawable/dummy_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/gotham_bold"
android:text="Dobby"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/image" />

<TextView
android:id="@+id/txt_des"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:fontFamily="@font/gotham_narrow_bold"
android:text="English Cocker Spaniel"
android:textColor="@color/txt_gray"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/img_edit_pet"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title" />

<ImageView
android:id="@+id/img_edit_pet"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginEnd="8dp"
android:scaleType="fitXY"
android:src="@drawable/ic_edit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/btn_start"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="50dp"
android:layout_marginBottom="24dp"
android:background="@drawable/btn_login"
android:drawableRight="@drawable/ic_play"
android:fontFamily="@font/gotham_narrow_bold"
android:paddingStart="32sp"
android:paddingTop="18dp"
android:paddingEnd="32sp"
android:paddingBottom="18dp"
android:text="@string/start_survey"
android:textAlignment="viewStart"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/title" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

</LinearLayout>

We are done with the adapter now let’s back to our MainActivity and finish that part as well.

We now have to implement the indicator below the view pager so let's start with that: First of all, we will get the count of indicators from an adapter which we have created previously by just calling as you can see below. Then we will create image views dynamically and will add those image views to the Linear Layout called “sliderDotspanel” and finally we will set image drawables for the selected and non-selected indicators

dotscount = adapter.count

val dots = arrayOfNulls<ImageView>(dotscount)

for (i in 0 until dotscount) {
dots[i] = ImageView(this)
dots[i]!!.setImageDrawable(
ContextCompat.getDrawable(this,
R.drawable.non_active_dot))
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.setMargins(8, 0, 8, 0)
sliderDotspanel!!.addView(dots[i], params)
}
dots[0]?.setImageDrawable(
ContextCompat.getDrawable(
this, R.drawable.active_dot)
)

Final Step:

We have created View Pager, it’s Adapter, Layouts and every thing now we just have to connect the indicators with viewpager to listen for changes in views on swipe for that we have to “setOnPageChangeListener” that will be invoked whenever the page changes or is incrementally scrolled and override its methods.

viewPager.setOnPageChangeListener(object: ViewPager.OnPageChangeListener{
override fun onPageScrollStateChanged(state: Int) {}

override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int){}

override fun onPageSelected(position: Int) {
for (i in 0 until dotscount) {
dots[i]?.setImageDrawable(
ContextCompat.getDrawable(
this@MainActivity,R.drawable.non_active_dot)
)
}
dots[position]?.setImageDrawable(
ContextCompat.getDrawable(
this@MainActivity,R.drawable.active_dot)
)
}
})

“onPageSelected” method will be invoked when a new page becomes selected and on every page selection, we have to move our indicator with respect to selected positions.

Bingo That’s it for now. You can refer to my GitHub repo for the resources used in this article and for the complete project.
https://github.com/aasif1297/CardwipeView

--

--

Full Stack Mobile App Developer — Flutter | Android | Laravel | Node JS | Technical Writer | Open Source Contributor