Pager dans Compose

Pour parcourir le contenu à gauche et à droite, ou de haut en bas, vous pouvez utiliser respectivement les composables HorizontalPager et VerticalPager. Ces composables ont des fonctions semblables à celles de ViewPager dans le système de vues. Par défaut, HorizontalPager occupe toute la largeur de l'écran, VerticalPager occupe toute la hauteur, et les bipeurs ne font glisser qu'une page à la fois. Vous pouvez configurer ces paramètres par défaut.

HorizontalPager

Pour créer un pager qui défile horizontalement, vers la gauche et vers la droite, utilisez HorizontalPager:

Figure 1 : Démonstration de 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:

Figure 2 : Démonstration de 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 figurant à la fois dans HorizontalPager et VerticalPager sont composées en différé 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, la page 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 la page jusqu'à un élément

Pour faire défiler la page jusqu'à une certaine page, 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")
}

Pour 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 une notification en cas de changement d'état de la page

PagerState comporte trois propriétés avec des informations sur les pages : currentPage, settledPage et targetPage.

  • currentPage: page la plus proche de la position d'ancrage. 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 ou aucun défilement n'est en cours d'exécution. Elle est différente de la propriété currentPage dans la mesure où currentPage est immédiatement mis à jour si la page est suffisamment proche de la position d'ancrage, mais settledPage reste le même 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 lors de chaque modification 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 afin d'obtenir des informations sur la page sélectionnée par rapport au nombre de pages, puis dessinez votre indicateur personnalisé.

Par exemple, si vous souhaitez un indicateur de cercle simple, vous pouvez répéter le nombre de cercles et modifier la couleur du cercle selon que la page est sélectionnée, à 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)
        )
    }
}

Pager montrant un indicateur circulaire sous le contenu
Figure 3. Bipeur affichant un indicateur circulaire sous le contenu

Appliquer des effets de défilement des éléments au contenu

Un cas d'utilisation courant consiste à utiliser la position de défilement pour appliquer des effets à vos éléments de bipeur. Pour connaître la distance entre une page et la page actuellement 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.

Figure 4 : Application de transformations au contenu du bipeur

Par exemple, pour ajuster l'opacité des éléments en fonction de leur distance par rapport au centre, modifiez le alpha en utilisant Modifier.graphicsLayer sur un élément à l'intérieur 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 la hauteur maximale. Vous pouvez définir la variable pageSize pour qu'elle ait un Fixed, un Fill (par défaut) ou un calcul de taille personnalisé.

Par exemple, pour définir une page 100.dp à largeur fixe:

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 l'élément 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 prennent tous deux en charge la modification de la marge intérieure du contenu, ce qui vous permet d'influencer la taille et l'alignement maximales des pages.

Par exemple, si vous définissez la marge intérieure start, les pages sont alignées vers la fin:

Pager avec une marge intérieure de début montrant le contenu aligné 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 la marge intérieure start et end, l'élément est centré horizontalement:

Pager avec une marge intérieure de début et de fin affichant le contenu centré

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(horizontal = 32.dp),
) { page ->
    // page content
}

Définir la marge intérieure end aligne les pages vers le début:

Pager avec une marge intérieure au début et à la fin montrant le contenu aligné au 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 chacune des dimensions de marge intérieure sur n'importe quelle valeur.

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. Cependant, vous pouvez personnaliser et modifier les valeurs par défaut, telles que pagerSnapDistance ou flingBehaviour.

Distance d'ancrage

Par défaut, HorizontalPager et VerticalPager définissent le nombre maximal de pages qu'un geste de glissement d'un geste vif peut faire défiler jusqu'à une page à la fois. Pour modifier cela, définissez pagerSnapDistance sur le 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),
        beyondBoundsPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}