Pour parcourir le contenu horizontalement ou verticalement, vous pouvez utiliser les composables HorizontalPager et VerticalPager. Elles ont des fonctions similaires à ViewPager dans le système de vues. Par défaut, HorizontalPager occupe toute la largeur de l'écran et VerticalPager toute la hauteur. Les pagers ne lancent également qu'une seule page à la fois. Toutes ces valeurs par défaut sont configurables.
HorizontalPager
Pour créer un pager qui défile horizontalement vers la gauche et la droite, utilisez HorizontalPager :
HorizontalPager
// Display 10 items val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier.fillMaxWidth() ) }
VerticalPager
Pour créer un pager qui défile vers le haut et vers le bas, utilisez VerticalPager :
VerticalPager
// Display 10 items val pagerState = rememberPagerState(pageCount = { 10 }) VerticalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier.fillMaxWidth() ) }
Création différée
Les pages de HorizontalPager et VerticalPager sont composées de manière différée et mises en page si nécessaire. Lorsque l'utilisateur fait défiler les pages, le composable supprime toutes les pages qui ne sont plus nécessaires.
Charger plus de pages hors écran
Par défaut, le pager ne charge que les pages visibles à l'écran. Pour charger plus de pages hors écran, définissez beyondBoundsPageCount sur une valeur supérieure à zéro.
Faire défiler jusqu'à un élément dans le pager
Pour faire défiler l'écran jusqu'à une certaine page du pager, créez un objet PagerState à l'aide de rememberPagerState() et transmettez-le en tant que paramètre state au pager. Vous pouvez appeler PagerState#scrollToPage() sur cet état, dans un CoroutineScope :
val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier .fillMaxWidth() .height(100.dp) ) } // scroll to page val coroutineScope = rememberCoroutineScope() Button(onClick = { coroutineScope.launch { // Call scroll to on pagerState pagerState.scrollToPage(5) } }, modifier = Modifier.align(Alignment.BottomCenter)) { Text("Jump to Page 5") }
Si vous souhaitez animer la page, utilisez la fonction PagerState#animateScrollToPage() :
val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPager(state = pagerState) { page -> // Our page content Text( text = "Page: $page", modifier = Modifier .fillMaxWidth() .height(100.dp) ) } // scroll to page val coroutineScope = rememberCoroutineScope() Button(onClick = { coroutineScope.launch { // Call scroll to on pagerState pagerState.animateScrollToPage(5) } }, modifier = Modifier.align(Alignment.BottomCenter)) { Text("Jump to Page 5") }
Recevoir des notifications sur les changements d'état de la page
PagerState comporte trois propriétés contenant des informations sur les pages : currentPage, settledPage et targetPage.
currentPage: page la plus proche de la position d'alignement. Par défaut, la position d'ancrage se trouve au début de la mise en page.settledPage: numéro de page lorsqu'aucune animation ni aucun défilement n'est en cours. Cette propriété est différente decurrentPage, carcurrentPageest immédiatement mis à jour si la page est suffisamment proche de la position d'ancrage, tandis quesettledPagereste inchangé jusqu'à ce que toutes les animations soient terminées.targetPage: position d'arrêt proposée pour un mouvement de défilement.
Vous pouvez utiliser la fonction snapshotFlow pour observer les modifications apportées à ces variables et y réagir. Par exemple, pour envoyer un événement d'analyse à chaque changement de page, vous pouvez procéder comme suit :
val pagerState = rememberPagerState(pageCount = { 10 }) LaunchedEffect(pagerState) { // Collect from the a snapshotFlow reading the currentPage snapshotFlow { pagerState.currentPage }.collect { page -> // Do something with each page change, for example: // viewModel.sendPageSelectedEvent(page) Log.d("Page change", "Page changed to $page") } } VerticalPager( state = pagerState, ) { page -> Text(text = "Page: $page") }
Ajouter un indicateur de page
Pour ajouter un indicateur à une page, utilisez l'objet PagerState pour obtenir des informations sur la page sélectionnée parmi le nombre de pages, puis dessinez votre indicateur personnalisé.
Par exemple, pour créer un indicateur circulaire, vous pouvez répéter le nombre de cercles et modifier la couleur du cercle selon que la page est sélectionnée ou non, à l'aide de pagerState.currentPage :
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize() ) { page -> // Our page content Text( text = "Page: $page", ) } Row( Modifier .wrapContentHeight() .fillMaxWidth() .align(Alignment.BottomCenter) .padding(bottom = 8.dp), horizontalArrangement = Arrangement.Center ) { repeat(pagerState.pageCount) { iteration -> val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray Box( modifier = Modifier .padding(2.dp) .clip(CircleShape) .background(color) .size(16.dp) ) } }
Appliquer des effets de défilement d'éléments au contenu
Un cas d'utilisation courant consiste à utiliser la position de défilement pour appliquer des effets aux éléments de votre pager. Pour connaître la distance entre une page et la page sélectionnée, vous pouvez utiliser PagerState.currentPageOffsetFraction. Vous pouvez ensuite appliquer des effets de transformation à votre contenu en fonction de la distance par rapport à la page sélectionnée.
Par exemple, pour ajuster l'opacité des éléments en fonction de leur distance par rapport au centre, modifiez alpha à l'aide de Modifier.graphicsLayer sur un élément du pager :
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager(state = pagerState) { page -> Card( Modifier .size(200.dp) .graphicsLayer { // Calculate the absolute offset for the current page from the // scroll position. We use the absolute value which allows us to mirror // any effects for both directions val pageOffset = ( (pagerState.currentPage - page) + pagerState .currentPageOffsetFraction ).absoluteValue // We animate the alpha, between 50% and 100% alpha = lerp( start = 0.5f, stop = 1f, fraction = 1f - pageOffset.coerceIn(0f, 1f) ) } ) { // Card content } }
Tailles de page personnalisées
Par défaut, HorizontalPager et VerticalPager occupent respectivement toute la largeur ou toute la hauteur. Vous pouvez définir la variable pageSize pour qu'elle ait une valeur Fixed, Fill (par défaut) ou un calcul de taille personnalisé.
Par exemple, pour définir une page de largeur fixe de 100.dp :
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, pageSize = PageSize.Fixed(100.dp) ) { page -> // page content }
Pour dimensionner les pages en fonction de la taille de la fenêtre d'affichage, utilisez un calcul de taille de page personnalisé. Créez un objet PageSize personnalisé et divisez availableSpace par trois, en tenant compte de l'espacement entre les éléments :
private val threePagesPerViewport = object : PageSize { override fun Density.calculateMainAxisPageSize( availableSpace: Int, pageSpacing: Int ): Int { return (availableSpace - 2 * pageSpacing) / 3 } }
Marge intérieure du contenu
HorizontalPager et VerticalPager permettent tous deux de modifier la marge intérieure du contenu, ce qui vous permet d'influencer la taille maximale et l'alignement des pages.
Par exemple, définir la marge intérieure start aligne les pages vers la fin :
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(start = 64.dp), ) { page -> // page content }
Si vous définissez la même valeur pour les marges intérieures start et end, l'élément est centré horizontalement :
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(horizontal = 32.dp), ) { page -> // page content }
La définition de la marge intérieure end aligne les pages vers le début :
val pagerState = rememberPagerState(pageCount = { 4 }) HorizontalPager( state = pagerState, contentPadding = PaddingValues(end = 64.dp), ) { page -> // page content }
Vous pouvez définir les valeurs top et bottom pour obtenir des effets similaires pour VerticalPager. La valeur 32.dp n'est utilisée ici qu'à titre d'exemple. Vous pouvez définir n'importe quelle valeur pour chacune des dimensions de marge intérieure.
Personnaliser le comportement de défilement
Les composables HorizontalPager et VerticalPager par défaut spécifient le fonctionnement des gestes de défilement avec le pager. Toutefois, vous pouvez personnaliser et modifier les valeurs par défaut, telles que pagerSnapDistance ou flingBehavior.
Distance d'alignement
Par défaut, HorizontalPager et VerticalPager définissent le nombre maximal de pages qu'un geste de balayage peut faire défiler sur une page à la fois. Pour modifier ce comportement, définissez pagerSnapDistance sur flingBehavior :
val pagerState = rememberPagerState(pageCount = { 10 }) val fling = PagerDefaults.flingBehavior( state = pagerState, pagerSnapDistance = PagerSnapDistance.atMost(10) ) Column(modifier = Modifier.fillMaxSize()) { HorizontalPager( state = pagerState, pageSize = PageSize.Fixed(200.dp), beyondViewportPageCount = 10, flingBehavior = fling ) { PagerSampleItem(page = it) } }
Créer un pager à avance automatique
Cette section explique comment créer un pager à défilement automatique avec des indicateurs de page dans Compose. La collection d'éléments défile automatiquement à l'horizontale, mais les utilisateurs peuvent également balayer manuellement l'écran pour passer d'un élément à un autre. Si un utilisateur interagit avec le pager, la progression automatique s'arrête.
Exemple de base
Ensemble, les extraits suivants créent une implémentation de base d'un pager à avance automatique avec un indicateur visuel, où chaque page s'affiche dans une couleur différente :
@Composable fun AutoAdvancePager(pageItems: List<Color>, modifier: Modifier = Modifier) { Box(modifier = Modifier.fillMaxSize()) { val pagerState = rememberPagerState(pageCount = { pageItems.size }) val pagerIsDragged by pagerState.interactionSource.collectIsDraggedAsState() val pageInteractionSource = remember { MutableInteractionSource() } val pageIsPressed by pageInteractionSource.collectIsPressedAsState() // Stop auto-advancing when pager is dragged or one of the pages is pressed val autoAdvance = !pagerIsDragged && !pageIsPressed if (autoAdvance) { LaunchedEffect(pagerState, pageInteractionSource) { while (true) { delay(2000) val nextPage = (pagerState.currentPage + 1) % pageItems.size pagerState.animateScrollToPage(nextPage) } } } HorizontalPager( state = pagerState ) { page -> Text( text = "Page: $page", textAlign = TextAlign.Center, modifier = modifier .fillMaxSize() .background(pageItems[page]) .clickable( interactionSource = pageInteractionSource, indication = LocalIndication.current ) { // Handle page click } .wrapContentSize(align = Alignment.Center) ) } PagerIndicator(pageItems.size, pagerState.currentPage) } }
Points clés concernant le code
- La fonction
AutoAdvancePagercrée une vue de pagination horizontale avec défilement automatique. Elle prend en entrée une liste d'objetsColor, qui sont utilisés comme couleurs d'arrière-plan pour chaque page. pagerStateest créé à l'aide derememberPagerState, qui contient l'état du pager.pagerIsDraggedetpageIsPressedsuivent l'interaction de l'utilisateur.LaunchedEffectfait avancer automatiquement le pager toutes les deux secondes, sauf si l'utilisateur le fait glisser ou appuie sur l'une des pages.HorizontalPageraffiche une liste de pages, chacune avec un composableTextaffichant le numéro de page. Le modificateur remplit la page, définit la couleur d'arrière-plan à partir depageItemset rend la page cliquable.
@Composable fun PagerIndicator(pageCount: Int, currentPageIndex: Int, modifier: Modifier = Modifier) { Box(modifier = Modifier.fillMaxSize()) { Row( modifier = Modifier .wrapContentHeight() .fillMaxWidth() .align(Alignment.BottomCenter) .padding(bottom = 8.dp), horizontalArrangement = Arrangement.Center ) { repeat(pageCount) { iteration -> val color = if (currentPageIndex == iteration) Color.DarkGray else Color.LightGray Box( modifier = modifier .padding(2.dp) .clip(CircleShape) .background(color) .size(16.dp) ) } } } }
Points clés concernant le code
- Un composable
Boxsert d'élément racine et contient unRowpour organiser les indicateurs de page horizontalement. - Un indicateur de page personnalisé s'affiche sous la forme d'une ligne de cercles, où chaque
Boxdécoupé enCircleShapereprésente une page. - Le cercle de la page actuelle est de couleur
DarkGray, tandis que les autres cercles sont de couleurLightGray. Le paramètrecurrentPageIndexdétermine quel cercle s'affiche en gris foncé.
Résultat
Cette vidéo montre le pager de base à avance automatique des extraits précédents :