xref: /aosp_15_r20/external/accompanist/docs/pager.md (revision fa44fe6ae8e729aa3cfe5c03eedbbf98fb44e2c6)
1# Pager layouts
2
3[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-pager)](https://search.maven.org/search?q=g:com.google.accompanist)
4
5A library which provides paging layouts for Jetpack Compose. If you've used Android's [`ViewPager`](https://developer.android.com/reference/kotlin/androidx/viewpager/widget/ViewPager) before, it has similar properties.
6
7!!! warning
8    **This library is deprecated, with official pager support in [androidx.compose.foundation.pager](https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/package-summary).** The original documentation is below the migration guide.
9
10## Migration
11
121. Make sure you are using Compose 1.4.0+ before attempting to migrate to `androidx.compose.foundation.pager`.
132. Change `com.google.accompanist.pager.HorizontalPager` to `androidx.compose.foundation.pager.HorizontalPager`, and the same for `com.google.accompanist.pager.VerticalPager` to change to `androidx.compose.foundation.pager.VerticalPager`
143. Change `count` variable to `pageCount`.
154. Change `itemSpacing` parameter to `pageSpacing`.
165. Change any usages of `rememberPagerState()` to `androidx.compose.foundation.pager.rememberPagerState()`
176. For more mappings - see the migration table below.
187. Run your changes on device and check to see if there are any differences.
19
20One thing to note is that there is a new parameter on `androidx.compose.foundation.Pager`, for `pageSize`, by default this
21uses a `PageSize.Fill`, but can also be changed to use a fixed size, like `PageSize.Fixed(200.dp)` for a fixed size paging.
22
23
24## Migration Table
25
26The following is a mapping of the pager classes from accompanist to androidx.compose:
27
28| accompanist/pager                    | androidx.compose.foundation                                                                                                                         |
29|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
30| `HorizontalPager`                    | `androidx.compose.foundation.pager.HorizontalPager`                                                                                                 |
31| `VerticalPager`                      | `androidx.compose.foundation.pager.VerticalPager`                                                                                                   |
32| `rememberPagerState`                 | `androidx.compose.foundation.pager.rememberPagerState`                                                                                              |
33| `PagerState#pageCount`               | Use `canScrollForward` or `canScrollBackward`                                                                                                       |
34| `calculateCurrentOffsetForPage`      | Use `(pagerState.currentPage - page) + pagerState.currentPageOffsetFraction`                                                                        |
35| `PagerState#currentPageOffset`       | `PagerState#currentPageOffsetFraction`                                                                                                              |
36| `Modifier.pagerTabIndicatorOffset()` | Implement it yourself, or still include and use `accompanist-pager-indicators`, it now supports `androidx.compose.foundation.pager.PagerState`      |
37| `HorizontalPagerIndicator`           | Implement it yourself, or fork `accompanist-pager-indicators` implementation |
38| `VerticalPagerIndicator`             | Implement it yourself, or fork `accompanist-pager-indicators` implementation |
39| `PagerDefaults.flingBehavior()`      | `androidx.compose.foundation.pager.PagerDefaults.flingBehavior()`                                                                                   |
40
41The biggest change is that `HorizontalPager` and `VerticalPager`'s number of pages is now called `pageCount` instead of `count`.
42
43# Deprecated Guidance for Accompanist Pager
44The following is the deprecated guide for using Pager in Accompanist. Please see above migration section for how to use the `androidx.compose` Pager.
45
46## HorizontalPager
47
48[`HorizontalPager`][api-horizpager] is a layout which lays out items in a horizontal row, and allows the user to horizontally swipe between pages.
49
50<figure>
51    <video width="300" controls loop>
52    <source src="horiz_demo.mp4" type="video/mp4">
53        Your browser does not support the video tag.
54    </video>
55    <figcaption>HorizontalPager demo</figcaption>
56</figure>
57
58The simplest usage looks like the following:
59
60``` kotlin
61// Display 10 items
62HorizontalPager(count = 10) { page ->
63    // Our page content
64    Text(
65        text = "Page: $page",
66        modifier = Modifier.fillMaxWidth()
67    )
68}
69```
70
71If you want to jump to a specific page, you either call call `pagerState.scrollToPage(index)` or  `pagerState.animateScrollToPage(index)` method in a `CoroutineScope`.
72
73``` kotlin
74val pagerState = rememberPagerState()
75
76HorizontalPager(count = 10, state = pagerState) { page ->
77    // ...page content
78}
79
80// Later, scroll to page 2
81scope.launch {
82    pagerState.scrollToPage(2)
83}
84```
85
86## VerticalPager
87
88[`VerticalPager`][api-vertpager] is very similar to [`HorizontalPager`][api-horizpager] but items are laid out vertically, and react to vertical swipes:
89
90<figure>
91    <video width="300" controls loop>
92    <source src="vert_demo.mp4" type="video/mp4">
93        Your browser does not support the video tag.
94    </video>
95    <figcaption>VerticalPager demo</figcaption>
96</figure>
97
98``` kotlin
99// Display 10 items
100VerticalPager(count = 10) { page ->
101    // Our page content
102    Text(
103        text = "Page: $page",
104        modifier = Modifier.fillMaxWidth()
105    )
106}
107```
108
109## Lazy creation
110
111Pages in both [`HorizontalPager`][api-horizpager] and [`VerticalPager`][api-vertpager] are lazily composed and laid-out as required by the layout. As the user scrolls through pages, any pages which are no longer required are removed from the content.
112
113Under the covers, `HorizontalPager` use [`LazyRow`](https://developer.android.com/jetpack/compose/lists#lazy), and `VerticalPager` uses [`LazyColumn`](https://developer.android.com/jetpack/compose/lists#lazy).
114
115
116## Content Padding
117
118`HorizontalPager` and `VerticalPager` both support the setting of content padding, which allows you to influence the maximum size and alignment of pages.
119
120You can see how different content padding values affect a `HorizontalPager` below:
121
122=== "start = 64.dp"
123
124    Setting the start padding has the effect of aligning the pages towards the end.
125
126    ![](contentpadding-start.png){: loading=lazy width=70% align=center }
127
128    ``` kotlin
129    HorizontalPager(
130        count = 4,
131        contentPadding = PaddingValues(start = 64.dp),
132    ) { page ->
133        // page content
134    }
135    ```
136
137=== "horizontal = 32.dp"
138
139    Setting both the start and end padding to the same value has the effect of centering the item horizontally.
140
141    ![](contentpadding-horizontal.png){: loading=lazy width=70% align=center }
142
143    ``` kotlin
144    HorizontalPager(
145        count = 4,
146        contentPadding = PaddingValues(horizontal = 32.dp),
147    ) { page ->
148        // page content
149    }
150    ```
151
152=== "end = 64.dp"
153
154    Setting the end padding has the effect of aligning the pages towards the start.
155
156    ![](contentpadding-end.png){: loading=lazy width=70% align=center }
157
158    ``` kotlin
159    HorizontalPager(
160        count = 4,
161        contentPadding = PaddingValues(end = 64.dp),
162    ) { page ->
163        // page content
164    }
165    ```
166
167Similar effects for `VerticalPager` can be achieved by setting the `top` and `bottom` values. The value `32.dp` is only used
168here as an example, you can set each of the padding dimensions to whatever value you wish.
169
170## Item scroll effects
171
172A common use-case is to apply effects to your pager items, using the scroll position to drive those effects.
173
174The [HorizontalPagerTransitionSample](https://github.com/google/accompanist/blob/main/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerTransitionSample.kt) demonstrates how this can be done:
175
176<figure>
177    <video width="300" controls loop>
178    <source src="transition_demo.mp4" type="video/mp4">
179        Your browser does not support the video tag.
180    </video>
181    <figcaption>Item effects demo</figcaption>
182</figure>
183
184
185The scope provided to your pager content allows apps to easily reference the [`currentPage`][currentpage-api] and [`currentPageOffset`][currentpageoffset-api]. The effects can then be calculated using those values. We provide the [`calculateCurrentOffsetForPage()`][calcoffsetpage] extension functions to support calculation of the 'offset' for a given page:
186
187``` kotlin
188import com.google.accompanist.pager.calculateCurrentOffsetForPage
189
190HorizontalPager(count = 4) { page ->
191    Card(
192        Modifier
193            .graphicsLayer {
194                // Calculate the absolute offset for the current page from the
195                // scroll position. We use the absolute value which allows us to mirror
196                // any effects for both directions
197                val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue
198
199                // We animate the scaleX + scaleY, between 85% and 100%
200                lerp(
201                    start = 0.85f,
202                    stop = 1f,
203                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
204                ).also { scale ->
205                    scaleX = scale
206                    scaleY = scale
207                }
208
209                // We animate the alpha, between 50% and 100%
210                alpha = lerp(
211                    start = 0.5f,
212                    stop = 1f,
213                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
214                )
215            }
216    ) {
217        // Card content
218    }
219}
220```
221
222## Reacting to page changes
223
224The [`PagerState.currentPage`][currentpage-api] property is updated whenever the selected page changes. You can use the `snapshotFlow` function to observe changes in a flow:
225
226``` kotlin
227val pagerState = rememberPagerState()
228
229LaunchedEffect(pagerState) {
230    // Collect from the pager state a snapshotFlow reading the currentPage
231    snapshotFlow { pagerState.currentPage }.collect { page ->
232        AnalyticsService.sendPageSelectedEvent(page)
233    }
234}
235
236VerticalPager(
237    count = 10,
238    state = pagerState,
239) { page ->
240    Text(text = "Page: $page")
241}
242```
243
244## Indicators
245
246We also publish a sibling library called `pager-indicators` which provides some simple indicator composables for use with [`HorizontalPager`][api-horizpager] and [`VerticalPager`][api-vertpager].
247
248<figure>
249    <video width="300" controls loop>
250    <source src="indicators_demo.mp4" type="video/mp4">
251        Your browser does not support the video tag.
252    </video>
253    <figcaption>Pager indicators demo</figcaption>
254</figure>
255
256The [HorizontalPagerWithIndicatorSample](https://github.com/google/accompanist/blob/main/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerWithIndicatorSample.kt) and [VerticalPagerWithIndicatorSample](https://github.com/google/accompanist/blob/snapshot/sample/src/main/java/com/google/accompanist/sample/pager/VerticalPagerWithIndicatorSample.kt) show you how to use these.
257
258
259### Integration with Tabs
260
261A common use-case for [`HorizontalPager`][api-horizpager] is to be used in conjunction with a [`TabRow`](https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#tabrow) or [`ScrollableTabRow`](https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#scrollabletabrow).
262
263<figure>
264    <video width="300" controls loop>
265    <source src="tabs_demo.mp4" type="video/mp4">
266        Your browser does not support the video tag.
267    </video>
268    <figcaption>HorizontalPager + TabRow</figcaption>
269</figure>
270
271
272Provided in the `pager-indicators` library is a modifier which can be used on a tab indicator like so:
273
274``` kotlin
275val pagerState = rememberPagerState()
276
277TabRow(
278    // Our selected tab is our current page
279    selectedTabIndex = pagerState.currentPage,
280    // Override the indicator, using the provided pagerTabIndicatorOffset modifier
281    indicator = { tabPositions ->
282        TabRowDefaults.Indicator(
283            Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
284        )
285    }
286) {
287    // Add tabs for all of our pages
288    pages.forEachIndexed { index, title ->
289        Tab(
290            text = { Text(title) },
291            selected = pagerState.currentPage == index,
292            onClick = { /* TODO */ },
293        )
294    }
295}
296
297HorizontalPager(
298    count = pages.size,
299    state = pagerState,
300) { page ->
301    // TODO: page content
302}
303```
304
305## Changes in v0.19.0
306
307In v0.19.0 both `HorizontalPager` and `VerticalPager` were re-written to be based on `LazyRow` and `LazyColumn` respectively. As part of this change, a number of feature and API changes were made:
308
309### PagerState
310
311- The `pageCount` parameter on `rememberPagerState()` has been removed, replaced with the `count` parameter on `HorizontalPager()` and `VerticalPager()`.
312- The `animationSpec`, `initialVelocity` and `skipPages` parameters on `animateScrollToPage()` have been removed. The lazy components handle this automatically.
313
314### HorizontalPager & VerticalPager
315
316- Ability to set `contentPadding` (see [above](#content-padding)).
317- Ability to specify a `key` for each page.
318- The `horizontalAlignment` parameter on `HorizontalPager`, and the `verticalAlignment` parameter on `VerticalPager` have been removed. A similar effect can be implemented with an appropriate content padding (see [above](#content-padding)).
319- The `infiniteLooping` parameter and feature have been removed. A sample demonstrating how to achieve this effect can be found [here][looping-sample].
320- The `offscreenLimit` parameter has been removed. We no longer have control of what items are laid out 'off screen'.
321- The `dragEnabled` parameter has removed.
322- `PagerScope` (the page item scope) no longer implements `BoxScope`.
323
324---
325
326## Usage
327
328``` groovy
329repositories {
330    mavenCentral()
331}
332
333dependencies {
334    implementation "com.google.accompanist:accompanist-pager:<version>"
335
336    // If using indicators, also depend on
337    implementation "com.google.accompanist:accompanist-pager-indicators:<version>"
338}
339```
340
341### Library Snapshots
342
343Snapshots of the current development version of this library are available, which track the latest commit. See [here](../using-snapshot-version) for more information on how to use them.
344
345---
346
347## Contributions
348
349Please contribute! We will gladly review any pull requests.
350Make sure to read the [Contributing](../contributing) page first though.
351
352## License
353
354```
355Copyright 2021 The Android Open Source Project
356
357Licensed under the Apache License, Version 2.0 (the "License");
358you may not use this file except in compliance with the License.
359You may obtain a copy of the License at
360
361    https://www.apache.org/licenses/LICENSE-2.0
362
363Unless required by applicable law or agreed to in writing, software
364distributed under the License is distributed on an "AS IS" BASIS,
365WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
366See the License for the specific language governing permissions and
367limitations under the License.
368```
369
370  [api-vertpager]: ../api/pager/pager/com.google.accompanist.pager/-vertical-pager.html
371  [api-horizpager]: ../api/pager/pager/com.google.accompanist.pager/-horizontal-pager.html
372  [currentpage-api]: ../api/pager/pager/com.google.accompanist.pager/-pager-state/current-page.html
373  [currentpageoffset-api]: ../api/pager/pager/com.google.accompanist.pager/-pager-state/current-page-offset.html
374  [calcoffsetpage]: ../api/pager/pager/com.google.accompanist.pager/calculate-current-offset-for-page.html
375  [pagerstate-api]: ../api/pager/pager/com.google.accompanist.pager/remember-pager-state.html
376  [looping-sample]: https://github.com/google/accompanist/blob/main/sample/src/main/java/com/google/accompanist/sample/pager/HorizontalPagerLoopingSample.kt
377