{"id":46034062,"url":"https://github.com/ryanw-mobile/compose-pager-demo","last_synced_at":"2026-03-01T04:32:17.116Z","repository":{"id":174049280,"uuid":"630205250","full_name":"ryanw-mobile/compose-pager-demo","owner":"ryanw-mobile","description":"Jetpack Compose Endless Horizontal Pager Animation Demo","archived":false,"fork":false,"pushed_at":"2026-02-12T04:49:40.000Z","size":7704,"stargazers_count":79,"open_issues_count":1,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-12T11:59:57.063Z","etag":null,"topics":["android","compose-viewpager","jetpack-compose","kotlin-android"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanw-mobile.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-04-19T22:26:36.000Z","updated_at":"2026-02-12T04:49:42.000Z","dependencies_parsed_at":"2026-01-16T20:02:12.467Z","dependency_job_id":null,"html_url":"https://github.com/ryanw-mobile/compose-pager-demo","commit_stats":null,"previous_names":["ryanwong-uk/compose-pager-demo","ryanw-mobile/compose-pager-demo"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ryanw-mobile/compose-pager-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanw-mobile%2Fcompose-pager-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanw-mobile%2Fcompose-pager-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanw-mobile%2Fcompose-pager-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanw-mobile%2Fcompose-pager-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanw-mobile","download_url":"https://codeload.github.com/ryanw-mobile/compose-pager-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanw-mobile%2Fcompose-pager-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29960253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T01:47:18.291Z","status":"online","status_checked_at":"2026-03-01T02:00:07.437Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","compose-viewpager","jetpack-compose","kotlin-android"],"created_at":"2026-03-01T04:32:16.693Z","updated_at":"2026-03-01T04:32:17.105Z","avatar_url":"https://github.com/ryanw-mobile.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Jetpack Compose Endless Horizontal Pager Animation Demo ![Gradle Build](https://github.com/ryanw-mobile/compose-pager-demo/actions/workflows/main_build.yml/badge.svg) [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com/)\n\nComplementary article:\n\n* [Reasons to Love the New Jetpack Compose Pager](https://medium.com/@callmeryan/reasons-to-love-the-new-jetpack-compose-pager-a53366fb6906)\n* [Implementing an Endless Pager in Jetpack Compose](https://medium.com/@callmeryan/implementing-an-endless-pager-in-jetpack-compose-bbb509a434b6)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"screenshots/240801_animated_wide.gif\" width=\"320\" alt=\"animated screenshot\"/\u003e\n\u003c/p\u003e\n\nThis is an app demonstrating the official Jetpack Compose Horizontal Pager.\n\nThis app shows how straightforward we can set up a Horizontal Pager, feed in whatever content we want, and apply animations.\n\nNo more custom views, adapters, fragments and complex lifecycle handling! Imagine how much extra work you need to build this using XML Views?\n\n\u0026nbsp;\u0026nbsp;\n\n## Animations\n\nThe page animations are all done using the `graphicsLayer` modifier at the page composable. It calculates the offset of that specific page relative to the current active page, and applies transformations.\n\n```\n Card(\n        modifier = modifier\n            .graphicsLayer {\n                val pageOffset = (\n                        (pagerState.currentPage - thisPageIndex) + pagerState\n                            .currentPageOffsetFraction\n                        )\n\n                alpha = lerp(\n                    start = 0.4f,\n                    stop = 1f,\n                    fraction = 1f - pageOffset.absoluteValue.coerceIn(0f, 1f),\n                )\n\n                cameraDistance = 8 * density\n                rotationY = lerp(\n                    start = 0f,\n                    stop = 40f,\n                    fraction = pageOffset.coerceIn(-1f, 1f),\n                )\n\n                lerp(\n                    start = 0.5f,\n                    stop = 1f,\n                    fraction = 1f - pageOffset.absoluteValue.coerceIn(0f, 1f),\n                ).also { scale -\u003e\n                    scaleX = scale\n                    scaleY = scale\n                }\n            }\n    )\n```\n\nTo make the page composable cleaner and not tied to the pager \u0026 animations, I have defined a custom `Modifier.pagerAnimation()` which is equivalent to the code above. You can find it at `com.rwmobi.composepager.ui.PagerAnimationModifier`. \n\nTo make the coupling looser, as the best practice, the `PageLayout` composable has a `modifier` parameter, so we only have to apply the `pagerAnimation` modifier when calling it from the `HorizontalPager()`, without a need to pass the `pagerState` to the `PageLayout`.\n\n\u0026nbsp;\n\n\u0026nbsp;\u0026nbsp;\n\n## Haptic Feedback\n\nLet's try `CompositionLocal`! We can perform haptic feedback in two lines of code.\n\nThe following `LaunchedEffect` can perform haptic feedback during a page-change event. You may do some extra work related to the page change within the same collector. \n\n\n```\n    var currentPageIndex by remember { mutableStateOf(0) }\n    val hapticFeedback = LocalHapticFeedback.current\n    LaunchedEffect(pagerState) {\n        snapshotFlow { pagerState.currentPage }.collect { currentPage -\u003e\n            // This is required to avoid the trigger when the pager is first loaded\n            if (currentPageIndex != currentPage) {\n                hapticFeedback.performHapticFeedback(hapticFeedbackType = HapticFeedbackType.LongPress)\n                currentPageIndex = currentPage\n            }\n            // Anything to be triggered by page-change can be done here\n        }\n    }\n```\n\nThe `snapshotFlow` approach was recommended by the previous Accompanist documentation.\n\n\u0026nbsp;\n\u0026nbsp;\n\n## Endless Pager\n\nBy manipulating the `pagerState`, we can make the pager scroll endlessly. We simply multiply the original number of pages by a relatively large number, set the `initialPage` to around the middle of the range, and then, when we need to resolve the index for contents, we take the remainder of the multiplied page index divided by the actual number of items, and we are good to go.\n\n```\n    val endlessPagerMultiplier = 1000\n    val pageCount = endlessPagerMultiplier * drawables.size\n    val initialPage = pageCount / 2\n\n    val pagerState = rememberPagerState(\n        initialPage = initialPage,\n        initialPageOffsetFraction = 0f,\n        pageCount = { pageCount },\n    )\n    \n    ...\n    \n    val resolvedPageContentIndex = absolutePageIndex % drawables.size\n```\n\n\n## Let's download and run it!\n\nThis project was configured to build using Android Studio Otter 3 Feature Drop | 2025.2.3. You will need to have Java 21 to build the project.\n\nAlternatively, you can find the ready-to-install APKs and App Bundles under the [release section](https://github.com/ryanw-mobile/compose-pager-demo/releases).\n\n## Technical details\n\n### Dependencies\n\n* [AndroidX Core KTX](https://developer.android.com/jetpack/androidx/releases/core) - Apache 2.0 - Kotlin extensions for Android core libraries\n* [JUnit](https://junit.org/junit4/) - EPL-1.0 - Unit testing framework for Java\n* [AndroidX Test Ext JUnit](https://developer.android.com/jetpack/androidx/releases/test) - Apache 2.0 - Extensions for JUnit in Android testing\n* [AndroidX Espresso Core](https://developer.android.com/training/testing/espresso) - Apache 2.0 - UI testing framework for Android\n* [AndroidX Lifecycle Runtime KTX](https://developer.android.com/jetpack/androidx/releases/lifecycle) - Apache 2.0 - Kotlin extensions for lifecycle-aware components\n* [AndroidX Activity Compose](https://developer.android.com/jetpack/androidx/releases/activity) - Apache 2.0 - Jetpack Compose integration with Activity\n* [Jetpack Compose BOM](https://developer.android.com/jetpack/compose/bom) - Apache 2.0 - Compose Bill of Materials for consistent versioning\n* [Jetpack Compose UI](https://developer.android.com/jetpack/compose) - Apache 2.0 - Fundamental UI components for Jetpack Compose\n* [Jetpack Compose Graphics](https://developer.android.com/jetpack/compose) - Apache 2.0 - Graphics utilities for Jetpack Compose\n* [Jetpack Compose Tooling](https://developer.android.com/jetpack/compose/tooling) - Apache 2.0 - Tooling support for Jetpack Compose\n* [Jetpack Compose UI Util](https://developer.android.com/jetpack/compose) - Apache 2.0 - Utility functions for Jetpack Compose UI\n* [Jetpack Compose Tooling Preview](https://developer.android.com/jetpack/compose/tooling) - Apache 2.0 - UI previews in Compose\n* [Jetpack Compose Test Manifest](https://developer.android.com/jetpack/compose/testing) - Apache 2.0 - Test manifest support for Jetpack Compose\n* [Jetpack Compose Test JUnit4](https://developer.android.com/jetpack/compose/testing) - Apache 2.0 - Compose UI testing with JUnit4\n* [Material3 for Jetpack Compose](https://developer.android.com/jetpack/compose/material3) - Apache 2.0 - Material Design 3 components for Compose\n* [AndroidX Test Rules](https://developer.android.com/jetpack/androidx/releases/test) - Apache 2.0 - JUnit rules for Android testing\n\n### Plugins\n\n* [Android Application Plugin](https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration) - Google - Plugin for building Android applications\n* [Compose Compiler Plugin](https://developer.android.com/jetpack/compose) - JetBrains - Plugin for Jetpack Compose\n* [Kotlin Android Plugin](https://kotlinlang.org/docs/gradle.html) - JetBrains - Plugin for Kotlin Android projects\n* [Detekt Plugin](https://detekt.dev/) - Artur Bosch - A static code analysis tool for Kotlin projects\n* [Kotlinter Plugin](https://github.com/jeremymailen/kotlinter-gradle) - Jeremy Mailen - Linter for Kotlin code\n* ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanw-mobile%2Fcompose-pager-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanw-mobile%2Fcompose-pager-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanw-mobile%2Fcompose-pager-demo/lists"}