Cercapersone in Scrivi

Per sfogliare i contenuti da sinistra a destra o dall'alto verso il basso, puoi utilizzare i composable HorizontalPager e VerticalPager, rispettivamente. Questi composable hanno funzioni simili a ViewPager nel sistema di visualizzazione. Per impostazione predefinita, HorizontalPager occupa l'intera larghezza dello schermo, VerticalPager occupa l'intera altezza e i pager scorrono solo una pagina alla volta. Tutti questi valori predefiniti sono configurabili.

HorizontalPager

Per creare un pager che scorre orizzontalmente a sinistra e a destra, utilizza HorizontalPager:

Figura 1. Demo di HorizontalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

VerticalPager

Per creare un pager che scorre verso l'alto e verso il basso, utilizza VerticalPager:

Figura 2. Demo di VerticalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
VerticalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

Lazy creation

Le pagine sia in HorizontalPager che in VerticalPager sono composte in modo differito e disposte quando necessario. Man mano che l'utente scorre le pagine, il componente rimovibile elimina le pagine che non sono più necessarie.

Caricare più pagine fuori dallo schermo

Per impostazione predefinita, il pager carica solo le pagine visibili sullo schermo. Per caricare più pagine fuori schermo, imposta beyondBoundsPageCount su un valore maggiore di zero.

Scorrere fino a un elemento nel pager

Per scorrere fino a una determinata pagina nel pager, crea un oggetto PagerState utilizzando rememberPagerState() e passalo come parametro state al pager. Puoi chiamare PagerState#scrollToPage() in questo stato, all'interno di 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")
}

Se vuoi animare la transizione alla pagina, utilizza la funzione 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")
}

Ricevere notifiche sulle modifiche dello stato della pagina

PagerState ha tre proprietà con informazioni sulle pagine: currentPage, settledPage e targetPage.

  • currentPage: La pagina più vicina alla posizione dello snap. Per impostazione predefinita, la posizione di aggancio si trova all'inizio del layout.
  • settledPage: il numero di pagina quando non è in esecuzione alcuna animazione o scorrimento. Questo è diverso dalla proprietà currentPage in quanto currentPage si aggiorna immediatamente se la pagina è abbastanza vicina alla posizione di snap, ma settledPage rimane invariato fino al termine dell'esecuzione di tutte le animazioni.
  • targetPage: La posizione di arresto proposta per un movimento di scorrimento.

Puoi utilizzare la funzione snapshotFlow per osservare le modifiche a queste variabili e reagire di conseguenza. Ad esempio, per inviare un evento Analytics a ogni cambio di pagina, puoi procedere nel seguente modo:

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")
}

Aggiungere un indicatore di pagina

Per aggiungere un indicatore a una pagina, utilizza l'oggetto PagerState per ottenere informazioni su quale pagina è selezionata rispetto al numero di pagine e disegna l'indicatore personalizzato.

Ad esempio, se vuoi un semplice indicatore a cerchio, puoi ripetere il numero di cerchi e cambiare il colore del cerchio in base alla selezione della pagina utilizzando 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)
        )
    }
}

Indicatore di pagina che mostra un cerchio sotto i contenuti
Figura 3. Pager che mostra un indicatore circolare sotto i contenuti

Applicare effetti di scorrimento degli elementi ai contenuti

Un caso d'uso comune è utilizzare la posizione di scorrimento per applicare effetti agli elementi del pager. Per scoprire a quale distanza si trova una pagina da quella attualmente selezionata, puoi utilizzare PagerState.currentPageOffsetFraction. Puoi quindi applicare effetti di trasformazione ai tuoi contenuti in base alla distanza dalla pagina selezionata.

Figura 4. Applicazione delle trasformazioni ai contenuti del pager

Ad esempio, per regolare l'opacità degli elementi in base alla distanza dal centro, modifica alpha utilizzando Modifier.graphicsLayer su un elemento all'interno del 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
    }
}

Dimensioni pagina personalizzate

Per impostazione predefinita, HorizontalPager e VerticalPager occupano rispettivamente l'intera larghezza o l'intera altezza. Puoi impostare la variabile pageSize in modo che abbia un valore Fixed, Fill (valore predefinito) o un calcolo personalizzato delle dimensioni.

Ad esempio, per impostare una pagina a larghezza fissa di 100.dp:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    pageSize = PageSize.Fixed(100.dp)
) { page ->
    // page content
}

Per dimensionare le pagine in base alle dimensioni dell'area visibile, utilizza un calcolo personalizzato delle dimensioni della pagina. Crea un oggetto PageSize personalizzato e dividi availableSpace per tre, tenendo conto della spaziatura tra gli elementi:

private val threePagesPerViewport = object : PageSize {
    override fun Density.calculateMainAxisPageSize(
        availableSpace: Int,
        pageSpacing: Int
    ): Int {
        return (availableSpace - 2 * pageSpacing) / 3
    }
}

Spazio interno dei contenuti

HorizontalPager e VerticalPager supportano entrambi la modifica del padding dei contenuti, che ti consente di influire sulle dimensioni e sull'allineamento massimi delle pagine.

Ad esempio, l'impostazione del padding start allinea le pagine verso la fine:

Pager con spaziatura iniziale che mostra i contenuti allineati verso la fine

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

Se imposti la stessa spaziatura interna sia per start che per end, l'elemento viene centrato orizzontalmente:

Pager con spaziatura interna iniziale e finale che mostra i contenuti centrati

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

L'impostazione del padding end allinea le pagine all'inizio:

Paginatore con spaziatura interna iniziale e finale che mostra i contenuti allineati all'inizio

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

Puoi impostare i valori top e bottom per ottenere effetti simili per VerticalPager. Il valore 32.dp viene utilizzato qui solo come esempio; puoi impostare ciascuna delle dimensioni del padding su qualsiasi valore.

Personalizzare il comportamento di scorrimento

I composable HorizontalPager e VerticalPager predefiniti specificano il funzionamento dei gesti di scorrimento con il pager. Tuttavia, puoi personalizzare e modificare le impostazioni predefinite, ad esempio pagerSnapDistance o flingBehavior.

Distanza di aggancio

Per impostazione predefinita, HorizontalPager e VerticalPager impostano il numero massimo di pagine che un gesto di scorrimento può scorrere a una pagina alla volta. Per modificare questa impostazione, imposta pagerSnapDistance su 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)
    }
}

Crea un pager ad avanzamento automatico

Questa sezione descrive come creare un pager a avanzamento automatico con indicatori di pagina in Compose. La raccolta di elementi scorre automaticamente in orizzontale, ma gli utenti possono anche scorrere manualmente tra gli elementi. Se un utente interagisce con il pager, la progressione automatica si interrompe.

Esempio di base

I seguenti snippet creano insieme un'implementazione di base di un pager con avanzamento automatico con un indicatore visivo, in cui ogni pagina viene visualizzata con un colore diverso:

@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)
    }
}

Punti chiave del codice

  • La funzione AutoAdvancePager crea una visualizzazione con paginazione orizzontale e avanzamento automatico. Prende come input un elenco di oggetti Color, che vengono utilizzati come colori di sfondo per ogni pagina.
  • pagerState viene creato utilizzando rememberPagerState, che contiene lo stato del pager.
  • pagerIsDragged e pageIsPressed tengono traccia dell'interazione degli utenti.
  • LaunchedEffect fa avanzare automaticamente il pager ogni due secondi, a meno che l'utente non lo trascini o non prema una delle pagine.
  • HorizontalPager mostra un elenco di pagine, ognuna con un componente componibile Text che mostra il numero di pagina. Il modificatore riempie la pagina, imposta il colore di sfondo da pageItems e rende la pagina cliccabile.

@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)
                )
            }
        }
    }
}

Punti chiave del codice

  • Un elemento componibile Box viene utilizzato come elemento principale.
    • All'interno di Box, un elemento componibile Row dispone gli indicatori di pagina orizzontalmente.
  • Un indicatore di pagina personalizzato viene visualizzato come una riga di cerchi, dove ogni Box ritagliato in un circle rappresenta una pagina.
  • Il cerchio della pagina corrente è colorato come DarkGray, mentre gli altri cerchi sono LightGray. Il parametro currentPageIndex determina quale cerchio viene visualizzato in grigio scuro.

Risultato

Questo video mostra il pager di avanzamento automatico di base degli snippet precedenti:

Figura 1. Un pager ad avanzamento automatico con un ritardo di due secondi tra ogni avanzamento di pagina.

Risorse aggiuntive