{"id":37024,"date":"2022-08-09T13:59:39","date_gmt":"2022-08-09T11:59:39","guid":{"rendered":"https:\/\/www.inovex.de\/?p=37024"},"modified":"2024-12-23T08:10:20","modified_gmt":"2024-12-23T07:10:20","slug":"custom-pager-indicator-jetpack-compose","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/","title":{"rendered":"How To Build a Custom Pager Indicator In Jetpack Compose"},"content":{"rendered":"<p>In this article, I describe what challenges you may face when you use the Accompanist Pager library when creating a custom pager indicator for a pager built in Jetpack Compose.<!--more--><\/p>\n<p>Recently, I had the chance to create a custom pager indicator for a pager built in Jetpack Compose using the Accompanist Pager library. Since the library is relatively new and there is not a lot to be found online apart from the official documentation, I would like to share the challenges I faced along the way.<\/p>\n<p>You can find the entire source code for this article on <a href=\"https:\/\/github.com\/jschamburger\/compose-custom-pagerindicator\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>.<\/p>\n<p>I used the latest stable version of the Accompanist Pager library which is 0.23.1. Please note that the APIs still change quite a bit (as expected with 0.x versions). At the time of writing, there is a release candidate for version 0.24.13. At the end of this article, there is a quick outlook on the coming changes.<\/p>\n<p>So, let&#8217;s get into it.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_83 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Pager-in-Compose\" >Pager in Compose<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#PagerIndicator\" >PagerIndicator<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Build-your-own-pager-indicator-with-Jetpack-Compose\" >Build your own pager indicator with Jetpack Compose<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#First-try-Just-calculate-the-current-and-target-width\" >First try: Just calculate the current and target width<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Problem-Glitch-when-aborting-a-scroll\" >Problem: Glitch when aborting a scroll<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Second-try-Calculate-targetPage-based-on-offset\" >Second try: Calculate targetPage based on offset<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Problem-Huge-indicators-when-scrolling-quickly\" >Problem: Huge indicators when scrolling quickly<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Solution-Calculate-currentPage-based-on-offset\" >Solution: Calculate currentPage based on offset<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Conclusion\" >Conclusion<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#Outlook\" >Outlook<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Pager-in-Compose\"><\/span>Pager in Compose<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Building pagers in Compose is very straightforward and the official documentation gives you everything you need to know. So let&#8217;s just start with a basic pager showing one image per page:<\/p>\n<pre class=\"lang:default decode:true\">        val pagerState = rememberPagerState()\r\n        val pages = listOf(\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2014\/02\/19\/20\/39\/winter-270160_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2019\/11\/23\/03\/08\/valley-4646114_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2018\/11\/29\/20\/01\/nature-3846403_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2016\/11\/19\/14\/38\/camel-1839616_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2014\/07\/23\/00\/56\/moon-399834_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2019\/12\/14\/18\/28\/sunrise-4695484_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2018\/03\/29\/07\/35\/water-3271579_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2021\/01\/23\/13\/01\/hills-5942468_1280.jpg\"),\r\n            Page(\"https:\/\/cdn.pixabay.com\/photo\/2019\/10\/09\/20\/18\/etretat-4538160_1280.jpg\"),\r\n        )        \r\n        HorizontalPager(\r\n            count = pages.size,\r\n            state = pagerState,\r\n            modifier = Modifier\r\n                .fillMaxSize()\r\n                .background(Color.LightGray)\r\n        ) { page -&gt;\r\n            Box(modifier = Modifier.fillMaxSize()) {\r\n                Surface(\r\n                    modifier = Modifier.fillMaxWidth()\r\n                ) {\r\n                    AsyncImage(\r\n                        model = pages[page].url,\r\n                        contentDescription = null\r\n                    )\r\n                }\r\n            }\r\n        }<\/pre>\n<p>(The pictures are taken from <a href=\"https:\/\/pixabay.com\/\" target=\"_blank\" rel=\"noopener\">pixabay.com<\/a>.)<\/p>\n<p>I suppose this does not need much explanation \u2013 feel free to play around with it in the sample project.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"PagerIndicator\"><\/span>PagerIndicator<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now, in order to see which item of the pager is currently selected, we would like to have a pager indicator. Adding one is fairly simple using the documentation. However, the indicators that come with the library are very limited. You can customize the colors, spacing and indicator shape, but that is about it.<\/p>\n<p>A few examples (which can also be found in the <a href=\"https:\/\/github.com\/jschamburger\/compose-custom-pagerindicator\" target=\"_blank\" rel=\"noopener\">sample project<\/a>, of course):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-37096 size-large aligncenter\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-485x1024.png\" alt=\"picture of pyramids\" width=\"485\" height=\"1024\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-485x1024.png 485w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-142x300.png 142w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-768x1621.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-728x1536.png 728w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-970x2048.png 970w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-400x844.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators-360x760.png 360w, https:\/\/www.inovex.de\/wp-content\/uploads\/PagerIndicators.png 1080w\" sizes=\"auto, (max-width: 485px) 100vw, 485px\" \/><\/p>\n<p>What I wanted to build was something like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-37049 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/solution.gif\" alt=\"Gif with pictures swiping\" width=\"320\" height=\"676\" \/><\/p>\n<p>Seems simple enough, doesn&#8217;t it? Well, unfortunately, it is not \u2026<\/p>\n<p>While the default indicator just moves an item around on a static background, here we need to actually change two items simultaneously. When changing pages, the previous item gets smaller and the next one gets larger.<\/p>\n<p>To achieve this, we have to \u2013 you guessed it \u2013 build a custom pager indicator. And we are doing it with Jetpack Compose.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Build-your-own-pager-indicator-with-Jetpack-Compose\"><\/span>Build your own pager indicator with Jetpack Compose<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"First-try-Just-calculate-the-current-and-target-width\"><\/span>First try: Just calculate the current and target width<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The <code>PagerState<\/code> gives us the current page number, the target page number and an offset. Using that, it should be fairly easy to calculate the width of the current and target page (all other widths are constant anyway).<\/p>\n<pre class=\"plain:false lang:default mark:10-11 decode:true \">private const val MULTIPLIER_SELECTED_PAGE = 4\r\nprivate val baseWidth = 4.dp\r\nprivate val spacing = 10.dp\r\nprivate val height = 8.dp\r\n\r\n@OptIn(ExperimentalPagerApi::class)\r\n@Composable\r\nfun CustomPagerIndicatorFirstTry(pagerState: PagerState, modifier: Modifier = Modifier, indicatorColor: Color = Color.Black) {\r\n    Row {\r\n        val currentPageWidth = baseWidth * (1 + (1 - abs(pagerState.currentPageOffset)) * MULTIPLIER_SELECTED_PAGE)\r\n        val targetPageWidth = baseWidth * (1 + abs(pagerState.currentPageOffset) * MULTIPLIER_SELECTED_PAGE)\r\n\r\n        repeat(pagerState.pageCount) { index -&gt;\r\n            val width = when (index) {\r\n                pagerState.currentPage -&gt; currentPageWidth\r\n                pagerState.targetPage -&gt; targetPageWidth\r\n                else -&gt; baseWidth\r\n            }\r\n            Box(\r\n                modifier = Modifier\r\n                    .width(width)\r\n                    .background(indicatorColor)\r\n                    .height(height)\r\n            )\r\n            if (index != pagerState.pageCount - 1) {\r\n                Spacer(modifier = Modifier.width(spacing))\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<p>So, we are calculating the width of the current and target pages based on the <code>currentPageOffset<\/code>. The calculation is quite straight-forward:<\/p>\n<ul>\n<li>If there is no offset at all, the current page is 5 times as wide as all the other pages.<\/li>\n<li>During scrolling, as the offset gets bigger, the width of the current page gets smaller while the width of the target page gets bigger. In the end, the current page has the same width as all inactive pages while the target page has 5 times the width of the other pages.<\/li>\n<\/ul>\n<p>In the rest of the code, we just draw boxes with background and spacing for all the pages we need.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-37047 size-full\" style=\"color: #404040;\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/first_try.gif\" alt=\"Gif showing a half-swipe of several pictures nect to each other\" width=\"320\" height=\"676\" \/><\/p>\n<h4><span class=\"ez-toc-section\" id=\"Problem-Glitch-when-aborting-a-scroll\"><\/span>Problem: Glitch when aborting a scroll<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Now, this already works quite well \u2013 the sizes are (almost) adjusted correctly. However, there is a glitch in the rendering as can be seen above: When aborting a scroll, i.e., starting to change the page but stopping the drag before the page actually changes, then the width of the current and target pages (and, by extension, the entire indicator) is too small for a moment.<\/p>\n<p>It took me a while to figure this out: The issue is that when scrolling through the pages, the target and current page numbers change. Let&#8217;s say we start scrolling from page 2 to 3: In the beginning, <code>currentPage<\/code>\u00a0is 2 while <code>targetPage<\/code> is 3, just as expected. But, if we lift our finger while being closer to page 2 than to page 3 (meaning that the pager is supposed to animate back to page 2 again), <code>currentPage<\/code>\u00a0and <code>targetPage<\/code> both point to 2. Of course, this makes sense because, at that moment, the target of the scroll is actually 2, but it complicates adjusting the width of page 3.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Second-try-Calculate-targetPage-based-on-offset\"><\/span>Second try: Calculate targetPage based on offset<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>So it seems that relying on\u00a0<code>targetPage<\/code> from <code>PagerState<\/code> is not possible for this. So let&#8217;s calculate it on our own:<\/p>\n<pre class=\"plain:false decode-attributes:false lang:default mark:5-8 decode:true\">@OptIn(ExperimentalPagerApi::class)\r\n@Composable\r\nfun CustomPagerIndicatorSecondTry(pagerState: PagerState, modifier: Modifier = Modifier, indicatorColor: Color = Color.Black) {\r\n    Row {\r\n        val currentPage = pagerState.currentPage\r\n        val targetPage = if (pagerState.currentPageOffset &lt; 0) currentPage - 1 else currentPage + 1\r\n        val currentPageWidth = baseWidth * (1 + (1 - abs(pagerState.currentPageOffset)) * MULTIPLIER_SELECTED_PAGE)\r\n        val targetPageWidth = baseWidth * (1 + abs(pagerState.currentPageOffset) * MULTIPLIER_SELECTED_PAGE)\r\n\r\n        repeat(pagerState.pageCount) { index -&gt;\r\n            val width = when (index) {\r\n                currentPage -&gt; currentPageWidth\r\n                targetPage -&gt; targetPageWidth\r\n                else -&gt; baseWidth\r\n            }\r\n            Box(\r\n                modifier = Modifier\r\n                    .width(width)\r\n                    .background(indicatorColor)\r\n                    .height(height)\r\n            )\r\n            if (index != pagerState.pageCount - 1) {\r\n                Spacer(modifier = Modifier.width(spacing))\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<p>Now, <code>targetPage<\/code> always points to the \u201coriginal\u201c target of the scroll, even if the user has stopped scrolling at a point which means that the pager will move back to the current page.<\/p>\n<p>This helps and in most cases, it all works fine now (feel free to try it using the sample project).<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Problem-Huge-indicators-when-scrolling-quickly\"><\/span>Problem: Huge indicators when scrolling quickly<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>However, when scrolling quite fast and with the correct timing between the drag motions, an odd effect can be seen:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-37078 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/second_try.gif\" alt=\"Gif showing fast scrolling pictures\" width=\"320\" height=\"676\" \/><\/p>\n<p>Actually, there was another assumption that I made above (without actually mentioning it): The offset is at all times supposed to be somewhere between -1 and 1. Bad news: it is not. When scrolling quickly, the absolute value of the offset can actually be greater than 1. In that case, <code>currentPage<\/code> somehow does not keep up with the scrolling. The logs can look something like this:<\/p>\n<pre>currentPage: 2 targetPage 1 offset -0.9296297\r\ncurrentPage: 2 targetPage 1 offset -0.9944445\r\ncurrentPage: 2 targetPage 1 offset -1.0592592\r\ncurrentPage: 2 targetPage 1 offset -1.1166667\r\ncurrentPage: 2 targetPage 1 offset -1.1611111\r\ncurrentPage: 2 targetPage 1 offset -1.1907408\r\ncurrentPage: 2 targetPage 1 offset -1.1953704\r\ncurrentPage: 2 targetPage 1 offset -1.237963\r\ncurrentPage: 2 targetPage 1 offset -1.2944444\r\ncurrentPage: 2 targetPage 1 offset -1.3592592\r\ncurrentPage: 2 targetPage 1 offset -1.425\r\ncurrentPage: 2 targetPage 1 offset -1.4907408\r\ncurrentPage: 2 targetPage 1 offset -1.5574074\r\ncurrentPage: 2 targetPage 1 offset -1.6148148\r\ncurrentPage: 2 targetPage 1 offset -1.6666666\r\ncurrentPage: 2 targetPage 1 offset -1.7120371\r\ncurrentPage: 2 targetPage 1 offset -1.7537037\r\ncurrentPage: 2 targetPage 1 offset -1.7898148\r\ncurrentPage: 2 targetPage 1 offset -1.8231481\r\ncurrentPage: 2 targetPage 1 offset -1.85\r\ncurrentPage: 2 targetPage 1 offset -1.9203703\r\ncurrentPage: 2 targetPage 1 offset -1.9805555\r\ncurrentPage: 2 targetPage 1 offset -2.0\r\ncurrentPage: 0 targetPage 1 offset 0.0<\/pre>\n<p>We see that <code>currentPage<\/code> is stuck at 2 but the offset continues to grow until at some point, the scroll settles and <code>currentPage<\/code> is updated to 0.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Solution-Calculate-currentPage-based-on-offset\"><\/span>Solution: Calculate currentPage based on offset<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Apparently, we need to calculate <code>currentPage<\/code> ourselves using the integer part of the offset:<\/p>\n<pre class=\"decode-attributes:false lang:default mark:5-10 decode:true \">@OptIn(ExperimentalPagerApi::class)\r\n@Composable\r\nfun CustomPagerIndicator(pagerState: PagerState, modifier: Modifier = Modifier, indicatorColor: Color = Color.Black) {\r\n    Row {\r\n        val offsetIntPart = pagerState.currentPageOffset.toInt()\r\n        val offsetFractionalPart = pagerState.currentPageOffset - offsetIntPart\r\n        val currentPage = pagerState.currentPage + offsetIntPart\r\n        val targetPage = if (pagerState.currentPageOffset &lt; 0) currentPage - 1 else currentPage + 1\r\n        val currentPageWidth = baseWidth * (1 + (1 - abs(offsetFractionalPart)) * MULTIPLIER_SELECTED_PAGE)\r\n        val targetPageWidth = baseWidth * (1 + abs(offsetFractionalPart) * MULTIPLIER_SELECTED_PAGE)\r\n\r\n        repeat(pagerState.pageCount) { index -&gt;\r\n            val width = when (index) {\r\n                currentPage -&gt; currentPageWidth\r\n                targetPage -&gt; targetPageWidth\r\n                else -&gt; baseWidth\r\n            }\r\n            Box(\r\n                modifier = Modifier\r\n                    .width(width)\r\n                    .background(indicatorColor)\r\n                    .height(height)\r\n            )\r\n            if (index != pagerState.pageCount - 1) {\r\n                Spacer(modifier = Modifier.width(spacing))\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<p>Using our calculated value for <code>currentPage<\/code>, the indicator finally behaves as expected:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-37080 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/solution-1.gif\" alt=\"Gif showing scrolling pictures\" width=\"320\" height=\"676\" \/><\/p>\n<p>As a small optimization, we can use <code>animateDpAsState<\/code> for the width of the items. This makes the page changes a bit smoother.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I am quite happy with the solution for this custom <code>PagerIndicator<\/code>. Also, the journey to get there was fun \u2013 it felt a bit like getting your hands dirty which does not happen very often with all the great libraries that offer everything you need.<\/p>\n<p>That being said, the solution feels rather complicated for such a seemingly simple problem. I suppose that most users are happy with the customization that the default indicator offers. But for those who need more, there is certainly room for improvement\u00a0in the Accompanist Pager library.<\/p>\n<p>As mentioned in the beginning, the code for this blog post uses version 0.23.1 of the Pager library. To get a complete picture, we should also take a look at the upcoming changes.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Outlook\"><\/span>Outlook<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Currently, there is a release candidate of version 0.24.13 for the Pager library. So let&#8217;s find out how the library&#8217;s behavior changed in the meantime.<\/p>\n<p>First of all, the new version does not have any breaking changes &#8211; I created a <a href=\"https:\/\/github.com\/jschamburger\/compose-custom-pagerindicator\/tree\/pager-library-0.24\" target=\"_blank\" rel=\"noopener\">branch in GitHub<\/a> which runs just fine.<\/p>\n<p>There are, however, a few notable differences:<\/p>\n<ul>\n<li>The glitch in the first version still persists. Actually, that is what I expected since the change of <code>currentPage<\/code> during the scroll makes sense.<\/li>\n<li>The second glitch is not reproducible. The reason for this is that <code>currentPage<\/code> is updated immediately when scrolling past half of the page (for details, check out the <a href=\"https:\/\/github.com\/google\/accompanist\/pull\/1047\" target=\"_blank\" rel=\"noopener\">pull request<\/a>). Apparently, this strange behavior was indeed a bug that is already fixed in the upcoming version.<\/li>\n<li>Also, <code>targetPage<\/code> is deprecated (<a href=\"https:\/\/github.com\/google\/accompanist\/pull\/1196\" target=\"_blank\" rel=\"noopener\">pull request<\/a>). So, in the future, we would need to calculate the target page ourselves anyway.<\/li>\n<\/ul>\n<p>In conclusion, the update fixes the issue with absolute offset values &gt;= 1 which makes implementation of a custom pager indicator as described in this article easier. But still, there is some manual effort to be done for calculating the <code>targetPage<\/code>.<\/p>\n<p>Hopefully, this article helps some people out there. I am looking forward to any comments or questions you might have!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, I describe what challenges you may face when you use the Accompanist Pager library when creating a custom pager indicator for a pager built in Jetpack Compose.<\/p>\n","protected":false},"author":242,"featured_media":37834,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[149,510,724,353],"service":[420],"coauthors":[{"id":242,"display_name":"Johannes Schamburger","user_nicename":"jschamburger"}],"class_list":["post-37024","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-android","tag-apps-2","tag-kotlin","tag-mobile","service-apps"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How To Build a Custom Pager Indicator In Jetpack Compose - inovex GmbH<\/title>\n<meta name=\"description\" content=\"This article shows which challenges you face when creating a custom pager indicator in Jetpack Compose with the Accompanist Pager library.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How To Build a Custom Pager Indicator In Jetpack Compose - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"This article shows which challenges you face when creating a custom pager indicator in Jetpack Compose with the Accompanist Pager library.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2022-08-09T11:59:39+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-12-23T07:10:20+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Johannes Schamburger\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Johannes Schamburger\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"11\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Johannes Schamburger\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/\"},\"author\":{\"name\":\"Johannes Schamburger\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/7d3af07f106f0e7f1d1414f7e4b8b44c\"},\"headline\":\"How To Build a Custom Pager Indicator In Jetpack Compose\",\"datePublished\":\"2022-08-09T11:59:39+00:00\",\"dateModified\":\"2024-12-23T07:10:20+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/\"},\"wordCount\":1276,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png\",\"keywords\":[\"Android\",\"Apps\",\"Kotlin\",\"Mobile\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/\",\"name\":\"How To Build a Custom Pager Indicator In Jetpack Compose - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png\",\"datePublished\":\"2022-08-09T11:59:39+00:00\",\"dateModified\":\"2024-12-23T07:10:20+00:00\",\"description\":\"This article shows which challenges you face when creating a custom pager indicator in Jetpack Compose with the Accompanist Pager library.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png\",\"width\":1920,\"height\":1080,\"caption\":\"Three image placeholders being scrolled through with a custom page indicator\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/custom-pager-indicator-jetpack-compose\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How To Build a Custom Pager Indicator In Jetpack Compose\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/7d3af07f106f0e7f1d1414f7e4b8b44c\",\"name\":\"Johannes Schamburger\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/IMG_3315_cropped-96x96.jpg54c1cc63e1170525fc8d2bb128512f74\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/IMG_3315_cropped-96x96.jpg\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/IMG_3315_cropped-96x96.jpg\",\"caption\":\"Johannes Schamburger\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/jschamburger\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How To Build a Custom Pager Indicator In Jetpack Compose - inovex GmbH","description":"This article shows which challenges you face when creating a custom pager indicator in Jetpack Compose with the Accompanist Pager library.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/","og_locale":"de_DE","og_type":"article","og_title":"How To Build a Custom Pager Indicator In Jetpack Compose - inovex GmbH","og_description":"This article shows which challenges you face when creating a custom pager indicator in Jetpack Compose with the Accompanist Pager library.","og_url":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2022-08-09T11:59:39+00:00","article_modified_time":"2024-12-23T07:10:20+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png","type":"image\/png"}],"author":"Johannes Schamburger","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Johannes Schamburger","Gesch\u00e4tzte Lesezeit":"11\u00a0Minuten","Written by":"Johannes Schamburger"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/"},"author":{"name":"Johannes Schamburger","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/7d3af07f106f0e7f1d1414f7e4b8b44c"},"headline":"How To Build a Custom Pager Indicator In Jetpack Compose","datePublished":"2022-08-09T11:59:39+00:00","dateModified":"2024-12-23T07:10:20+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/"},"wordCount":1276,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png","keywords":["Android","Apps","Kotlin","Mobile"],"articleSection":["Applications","English Content","General"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/","url":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/","name":"How To Build a Custom Pager Indicator In Jetpack Compose - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png","datePublished":"2022-08-09T11:59:39+00:00","dateModified":"2024-12-23T07:10:20+00:00","description":"This article shows which challenges you face when creating a custom pager indicator in Jetpack Compose with the Accompanist Pager library.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/How-To-Build-a-Custom-Pager-Indicator-In-Jetpack-Compose.png","width":1920,"height":1080,"caption":"Three image placeholders being scrolled through with a custom page indicator"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/custom-pager-indicator-jetpack-compose\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"How To Build a Custom Pager Indicator In Jetpack Compose"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/7d3af07f106f0e7f1d1414f7e4b8b44c","name":"Johannes Schamburger","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/wp-content\/uploads\/IMG_3315_cropped-96x96.jpg54c1cc63e1170525fc8d2bb128512f74","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/IMG_3315_cropped-96x96.jpg","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/IMG_3315_cropped-96x96.jpg","caption":"Johannes Schamburger"},"url":"https:\/\/www.inovex.de\/de\/blog\/author\/jschamburger\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/37024","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/242"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=37024"}],"version-history":[{"count":6,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/37024\/revisions"}],"predecessor-version":[{"id":60245,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/37024\/revisions\/60245"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/37834"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=37024"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=37024"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=37024"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=37024"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}