{"id":3741,"date":"2025-04-24T06:33:48","date_gmt":"2025-04-24T06:33:48","guid":{"rendered":"https:\/\/alsaeeddev.com\/?p=3741"},"modified":"2025-04-25T12:06:35","modified_gmt":"2025-04-25T12:06:35","slug":"card-animation-in-jetpack-compose","status":"publish","type":"post","link":"https:\/\/alsaeeddev.com\/shop\/card-animation-in-jetpack-compose\/","title":{"rendered":"Swipe and Tap Card Animation in Jetpack Compose"},"content":{"rendered":"<article>\nCreating an interactive card-based interface in Jetpack Compose is easier than ever. Today, we will explore how to implement a smooth Card Animation in Jetpack using swipe or tap gestures on cards with the CardSwipeOrTap composable. This approach is perfect for modern UI designs where users interact with content via gestures.<\/p>\n<p><strong>Main Activity Setup<\/strong><\/p>\n<p>We start by setting up the MainActivity that uses a Scaffold layout inside the RecyclerViewTheme. This ensures that our UI fills the entire screen while providing padding to our card interactions.<\/p>\n<pre><code>class MainActivity : ComponentActivity() {\r\n    override fun onCreate(savedInstanceState: Bundle?) {\r\n        super.onCreate(savedInstanceState)\r\n        setContent {\r\n            RecyclerViewTheme {\r\n                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -&gt;\r\n                    CardSwipeOrTap(modifier = Modifier.padding(innerPadding))\r\n                }\r\n            }\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<h3>Building the Card Swipe or Tap Composable<\/h3>\n<p>The <code>CardSwipeOrTap<\/code> composable manages the card stack. It uses a <code>Box<\/code> to overlay cards on top of each other and applies a slight rotation and vertical offset for a realistic stacked effect.<\/p>\n<p>Each card is individually rendered using the <code>CardItem<\/code> composable. We use a simple <strong>CoroutineScope<\/strong> to delay actions and allow for smooth animations between swipe or tap events.<\/p>\n<pre><code>@Composable\r\nfun CardSwipeOrTap(modifier: Modifier = Modifier) {\r\n    val scope = rememberCoroutineScope()\r\n    var cards by remember { mutableStateOf(listOf(0, 1, 2)) }\r\n    var isAnimating by remember { mutableStateOf(false) }\r\n\r\n    val cardColors = listOf(\r\n        Color(0xFFFF9800), \/\/ Orange\r\n        Color(0xFFF44336), \/\/ Red\r\n        Color(0xFF00E676)  \/\/ Light Green\r\n    )\r\n\r\n    Box(\r\n        modifier = modifier.fillMaxSize().padding(32.dp),\r\n        contentAlignment = Alignment.Center\r\n    ) {\r\n        cards.reversed().forEachIndexed { index, cardIndex -&gt;\r\n            CardItem(\r\n                index = cardIndex,\r\n                color = cardColors[cardIndex % cardColors.size],\r\n                rotation = if (index == 0) 0f else if (index == 1) -10f else 10f,\r\n                offsetY = (index * 20).dp,\r\n                isTop = index == cards.size - 1,\r\n                isAnimating = isAnimating,\r\n                onSwiped = {\r\n                    if (!isAnimating) {\r\n                        isAnimating = true\r\n                        scope.launch {\r\n                            kotlinx.coroutines.delay(300)\r\n                            val updated = cards.toMutableList()\r\n                            val removed = updated.removeAt(0)\r\n                            updated.add(removed)\r\n                            cards = updated\r\n                            isAnimating = false\r\n                        }\r\n                    }\r\n                }\r\n            )\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<h3>Animating Card Movement with CardItem<\/h3>\n<p>The <code>CardItem<\/code> composable handles the animation when a user swipes or taps a card. It uses <strong>animateDpAsState<\/strong> and <strong>animateFloatAsState<\/strong> to animate the card&#8217;s offset and opacity, giving the appearance of a disappearing card when an interaction occurs.<\/p>\n<pre><code>@Composable\r\nfun CardItem(\r\n    index: Int,\r\n    color: Color,\r\n    rotation: Float,\r\n    offsetY: Dp,\r\n    isTop: Boolean,\r\n    isAnimating: Boolean,\r\n    onSwiped: () -&gt; Unit\r\n) {\r\n    val animatedOffsetX by animateDpAsState(\r\n        targetValue = if (isAnimating &amp;&amp; isTop) 500.dp else 0.dp,\r\n        animationSpec = if (isAnimating &amp;&amp; isTop) tween(durationMillis = 500, easing = FastOutLinearInEasing) else snap(),\r\n        label = \"OffsetXAnimation\"\r\n    )\r\n\r\n    val animatedAlpha by animateFloatAsState(\r\n        targetValue = if (isAnimating &amp;&amp; isTop) 0f else 1f,\r\n        animationSpec = if (isAnimating &amp;&amp; isTop) tween(durationMillis = 3000) else snap(),\r\n        label = \"AlphaAnimation\"\r\n    )\r\n\r\n    Card(\r\n        modifier = Modifier\r\n            .fillMaxWidth(0.7f)\r\n            .aspectRatio(1f)\r\n            .offset(x = animatedOffsetX, y = offsetY)\r\n            .graphicsLayer {\r\n                rotationZ = rotation\r\n                alpha = animatedAlpha\r\n            }\r\n            .pointerInput(isTop) {\r\n                if (isTop &amp;&amp; !isAnimating) {\r\n                    detectDragGestures(\r\n                        onDragEnd = { onSwiped() },\r\n                        onDrag = { change, _ -&gt; change.consume() }\r\n                    )\r\n                }\r\n            }\r\n            .pointerInput(isTop) {\r\n                if (isTop &amp;&amp; !isAnimating) {\r\n                    detectTapGestures(\r\n                        onTap = { onSwiped() }\r\n                    )\r\n                }\r\n            },\r\n        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),\r\n        shape = RoundedCornerShape(20.dp),\r\n        colors = CardDefaults.cardColors(\r\n            containerColor = color,\r\n            contentColor = Color.White\r\n        )\r\n    ) {\r\n        Box(\r\n            modifier = Modifier.fillMaxSize(),\r\n            contentAlignment = Alignment.Center\r\n        ) {\r\n            Text(\r\n                text = \"Card #$index\",\r\n                style = MaterialTheme.typography.titleLarge,\r\n                color = Color.White\r\n            )\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<hr \/>\n<p><iframe loading=\"lazy\" title=\"Card Swipe Animation in Jetpack Compose \ud83d\udd25 | Android UI  | Free Source Code | #shorts #jetpack\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/TYCocTetzC4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<hr \/>\n<h4>Final Thoughts for Card Animation in Jetpack<\/h4>\n<p>With just a few lines of Kotlin code, you can create a stunning <strong>card swipe and tap animation<\/strong> in Jetpack Compose. This approach is not only visually appealing but also highly interactive, offering a smooth user experience without the complexity of a traditional RecyclerView setup. Try it out in your next Android app project!<\/p>\n<p>You can also read the official <a href=\"https:\/\/developer.android.com\/develop\/ui\/compose\/animation\/introduction\" target=\"_blank\" rel=\"noopener\">Jetpack Compose animation documentation<\/a>.<\/p>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Creating an interactive card-based interface in Jetpack Compose is easier than ever. Today, we will explore how to implement a smooth Card Animation in Jetpack using swipe or tap gestures on cards with the CardSwipeOrTap composable. This approach is perfect for modern UI designs where users interact with content via gestures. Main Activity Setup We [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3763,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,164],"tags":[97,111,107,102,104,98,103,106,113,101,110,114,96,105,108,109,115,99,100,112],"class_list":["post-3741","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","category-source-codes","tag-android-development","tag-android-jetpack","tag-android-motion","tag-android-ui-design","tag-animated-ui","tag-card-animation","tag-cardswipeortap","tag-compose-animation","tag-compose-transitions","tag-compose-ui","tag-gesture-detection","tag-interactive-cards","tag-jetpack-compose","tag-jetpack-compose-tutorial","tag-kotlin-ui","tag-material-design","tag-mobile-app-ui","tag-swipe-gesture","tag-tap-gesture","tag-ui-components"],"_links":{"self":[{"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/posts\/3741","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/comments?post=3741"}],"version-history":[{"count":10,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/posts\/3741\/revisions"}],"predecessor-version":[{"id":3750,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/posts\/3741\/revisions\/3750"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/media\/3763"}],"wp:attachment":[{"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/media?parent=3741"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/categories?post=3741"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/alsaeeddev.com\/shop\/wp-json\/wp\/v2\/tags?post=3741"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}