Implementing Card-Style Swipe Effect with Android ViewPager

This tutorial demonstrates how to create a visually appealing card-style swipe effect using Android's native ViewPager component. The effect creates a smooth transition between pages with scaling and translation animations.

Step 1: Create the Card Item Layout

The item layout serves as the template for each card displayed in the ViewPager:

<?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"
    android:background="@drawable/card_background"
    android:gravity="center">

    <ImageView
        android:id="@+id/card_image"
        android:paddingBottom="@dimen/spacing_large"
        android:paddingTop="@dimen/spacing_large"
        android:paddingLeft="@dimen/spacing_medium"
        android:paddingRight="@dimen/spacing_medium"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter" />

</RelativeLayout>

Step 2: Implement the PagerAdapter

The adapter manages the card views and provides them to the ViewPager:

class CardPagerAdapter(private val context: Context) : PagerAdapter() {

    private val cardImages = intArrayOf(
        R.drawable.card_1, R.drawable.card_2, R.drawable.card_3,
        R.drawable.card_4, R.drawable.card_5, R.drawable.card_6,
        R.drawable.card_7, R.drawable.card_8, R.drawable.card_9,
        R.drawable.card_10, R.drawable.card_11, R.drawable.card_12
    )

    override fun isViewFromObject(view: View, obj: Any): Boolean {
        return view === obj
    }

    override fun getCount(): Int {
        return TOTAL_CARDS * MULTIPLIER
    }

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val cardView = LayoutInflater.from(context)
            .inflate(R.layout.card_item_layout, container, false)

        val imageView = cardView.findViewById<ImageView>(R.id.card_image)
        imageView.setImageResource(cardImages[position % cardImages.size])

        container.addView(cardView)
        return cardView
    }


    override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
        container.removeView(obj as View)
    }

    companion object {
        private const val TOTAL_CARDS = 12
        private const val MULTIPLIER = 30
    }
}

Step 3: Create the Main Activity Layout

This layout contains the ViewPager component:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:fitsSystemWindows="true">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:overScrollMode="never"
        android:paddingBottom="@dimen/bottom_spacing"
        android:paddingLeft="@dimen/card_margin"
        android:paddingRight="@dimen/card_margin"
        android:paddingTop="@dimen/top_spacing" />

</RelativeLayout>

Step 4: Implement the PageTransformer

The PageTransformer applies the card scaling and translation animations during page transitions:

class CardPageTransformer : ViewPager2.PageTransformer {

    private val minScale = 0.85f
    private val maxScale = 1.0f
    private val offsetAdjustment = 0.15f

    override fun transformPage(page: View, position: Float) {
        val adjustedPosition = position - offsetAdjustment
        val absPosition = kotlin.math.abs(adjustedPosition)

        when {
            adjustedPosition <= 1 && adjustedPosition >= -1 -> {
                val scaleFactor = minScale + (1 - absPosition) * (maxScale - minScale)

                page.scaleX = scaleFactor
                page.scaleY = scaleFactor

                if (adjustedPosition > 0) {
                    page.translationX = -scaleFactor * TRANSLATION_FACTOR
                } else if (adjustedPosition < 0) {
                    page.translationX = scaleFactor * TRANSLATION_FACTOR
                }
            }
            else -> {
                page.scaleX = minScale
                page.scaleY = minScale
            }
        }
    }

    companion object {
        private const val TRANSLATION_FACTOR = 2.0f
    }
}

Step 5: Initialize the ViewPager in Activity

Configure the ViewPager with the adapter and transformer:

class MainActivity : AppCompatActivity() {

    private lateinit var viewPager: ViewPager2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewPager = findViewById(R.id.viewpager)
        
        viewPager.apply {
            adapter = CardPagerAdapter(this@MainActivity)
            offscreenPageLimit = PRELOAD_PAGES
            setPageTransformer(false, CardPageTransformer())
            currentItem = INITIAL_POSITION
        }
    }

    companion object {
        private const val PRELOAD_PAGES = 5
        private const val INITIAL_POSITION = 180
    }
}

Key Implementation Details

The card effect relies on several critical configurations:

  1. PageTransformer: Calculates the scale factor based on the page position relative to the current page. The formula minScale + (1 - absPosition) * (maxScale - minScale) creates the progressive scaling effect.

  2. Translation Adjustment: The condisional translation prevents cards from overlapping incorrectly during the swipe gesture.

  3. OffscreenPageLimit: Setting this to 5 ensures smooth scrolling through multiple cards without frequent view recreation.

  4. Initial Position: Starting at the middle of the dataset (12 * 15 = 180) allows users to swipe in both directions initially.

The result produces a smooth, iOS-style card carousel effect where cards scale down as they move away from the center position while maintaining proper spacing and overlap.

Tags: Android ViewPager UI Animation kotlin Mobile Development

Posted on Thu, 08 Oct 2026 16:13:15 +0000 by Vasudhevan