Pager in der Funktion „Schreiben“

Mit den zusammensetzbaren Funktionen HorizontalPager und VerticalPager können Sie horizontal oder vertikal durch Inhalte blättern. Sie haben ähnliche Funktionen wie ViewPager im Ansichtssystem. Standardmäßig nimmt HorizontalPager die gesamte Bildschirmbreite und VerticalPager die gesamte Höhe ein. Außerdem wird mit den Pager-Elementen jeweils nur eine Seite weitergeblättert. Alle diese Standardeinstellungen sind konfigurierbar.

HorizontalPager

Verwenden Sie HorizontalPager, um ein Pager-Element zu erstellen, das horizontal nach links und rechts scrollt:

Abbildung 1. Demo von HorizontalPager

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

VerticalPager

Verwenden Sie VerticalPager, um ein Pager-Element zu erstellen, das nach oben und unten scrollt:

Abbildung 2. Demo von VerticalPager

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

Lazy-Erstellung

Seiten in HorizontalPager und VerticalPager werden lazy zusammengesetzt und bei Bedarf angeordnet. Wenn der Nutzer durch die Seiten scrollt, werden alle nicht mehr benötigten Seiten von der zusammensetzbaren Funktion entfernt.

Weitere Seiten außerhalb des Bildschirms laden

Standardmäßig werden im Pager-Element nur die sichtbaren Seiten auf dem Bildschirm geladen. Wenn Sie weitere Seiten außerhalb des Bildschirms laden möchten, legen Sie für beyondBoundsPageCount einen Wert über null fest.

Zu einem Element im Pager-Element scrollen

Wenn Sie zu einer bestimmten Seite im Pager-Element scrollen möchten, erstellen Sie ein PagerState-Objekt mit rememberPagerState() und übergeben Sie es als state-Parameter an das Pager-Element. Sie können PagerState#scrollToPage() in diesem Status innerhalb eines CoroutineScope aufrufen:

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

Wenn Sie zur Seite animieren möchten, verwenden Sie die PagerState#animateScrollToPage() Funktion:

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

Benachrichtigungen bei Änderungen am Seitenstatus erhalten

PagerState hat drei Attribute mit Informationen zu Seiten: currentPage, settledPage und targetPage.

  • currentPage: Die Seite, die der Position am nächsten ist, an der das Scrollen beendet wird. Standardmäßig befindet sich die Andockposition am Anfang des Layouts.
  • settledPage: Die Seitenzahl, wenn keine Animation oder kein Scrollen ausgeführt wird. Dies unterscheidet sich vom Attribut currentPage, da currentPage sofort aktualisiert wird, wenn sich die Seite nahe genug an der Position befindet, an der das Scrollen beendet wird. settledPage bleibt jedoch gleich, bis alle Animationen abgeschlossen sind.
  • targetPage: Die vorgeschlagene Endposition für eine Scrollbewegung.

Mit der Funktion snapshotFlow können Sie Änderungen an diesen Variablen beobachten und darauf reagieren. Wenn Sie beispielsweise bei jeder Seitenänderung ein Analytics-Ereignis senden möchten, können Sie so vorgehen:

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

Seitenindikator hinzufügen

Wenn Sie einer Seite einen Indikator hinzufügen möchten, verwenden Sie das PagerState-Objekt, um Informationen dazu zu erhalten, welche Seite aus der Anzahl der Seiten ausgewählt ist, und zeichnen Sie Ihren benutzerdefinierten Indikator.

Wenn Sie beispielsweise einen Kreisindikator erstellen möchten, können Sie die Anzahl der Kreise wiederholen und die Farbe des Kreises ändern, je nachdem, ob die Seite ausgewählt ist. Verwenden Sie dazu 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 mit einem kreisförmigen Indikator unter dem Inhalt
Abbildung 3. Pager-Element mit einem Kreisindikator unter dem Inhalt

Scroll-Effekte auf Inhalte anwenden

Ein häufiger Anwendungsfall ist, die Scrollposition zu verwenden, um Effekte auf die Pager-Elemente anzuwenden. Mit PagerState.currentPageOffsetFraction können Sie herausfinden, wie weit eine Seite von der ausgewählten Seite entfernt ist. Anschließend können Sie Transformations-Effekte auf Ihre Inhalte anwenden, je nachdem, wie weit sie von der ausgewählten Seite entfernt sind.

Abbildung 4. Transformationen auf Pager-Inhalte anwenden

Wenn Sie beispielsweise die Deckkraft von Elementen anpassen möchten, je nachdem, wie weit sie vom Mittelpunkt entfernt sind, ändern Sie den Wert von alpha mit Modifier.graphicsLayer für ein Element im Pager-Element:

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

Benutzerdefinierte Seitengrößen

Standardmäßig nehmen HorizontalPager und VerticalPager die gesamte Breite bzw. Höhe ein. Sie können die Variable pageSize auf Fixed, Fill (Standard) oder eine benutzerdefinierte Größenberechnung festlegen.

So legen Sie beispielsweise eine Seite mit einer festen Breite von 100.dp fest:

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

Wenn Sie die Größe der Seiten an die Größe des Darstellungsbereichs anpassen möchten, verwenden Sie eine benutzerdefinierte Berechnung der Seitengröße. Erstellen Sie ein benutzerdefiniertes PageSize-Objekt und teilen Sie die availableSpace durch drei. Berücksichtigen Sie dabei den Abstand zwischen den Elementen:

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

Content-Padding

Sowohl HorizontalPager als auch VerticalPager unterstützen das Ändern des Content-Paddings. So können Sie die maximale Größe und Ausrichtung von Seiten beeinflussen.

Wenn Sie beispielsweise das start-Padding festlegen, werden die Seiten am Ende ausgerichtet:

Pager mit Start-Padding, bei dem der Inhalt am Ende ausgerichtet ist
Abbildung 5. Pager-Element mit Start-Padding.

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

Wenn Sie sowohl das start- als auch das end-Padding auf denselben Wert festlegen, wird das Element horizontal zentriert:

Pager mit Start- und End-Padding, in dem der Inhalt zentriert dargestellt wird
Abbildung 6. Pager-Element mit horizontalem Padding.

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

Wenn Sie das end-Padding festlegen, werden die Seiten am Anfang ausgerichtet:

Pager mit Start- und End-Padding, in dem der Inhalt am Anfang ausgerichtet ist
Abbildung 7. Pager-Element mit End-Padding.

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

Sie können die Werte top und bottom festlegen, um ähnliche Effekte für VerticalPager zu erzielen. Der Wert 32.dp wird hier nur als Beispiel verwendet. Sie können für jede der Padding-Dimensionen einen beliebigen Wert festlegen.

Scrollverhalten anpassen

Die Standard-Composables HorizontalPager und VerticalPager geben an, wie Scrollgesten mit dem Pager-Element funktionieren. Sie können jedoch die Standardeinstellungen anpassen und ändern, z. B. pagerSnapDistance oder flingBehavior.

Abstand für das Einrasten

Standardmäßig legen HorizontalPager und VerticalPager die maximale Anzahl von Seiten fest, die mit einer zügigen Wischbewegung weitergescrollt werden können. Es wird jeweils nur eine Seite weitergescrollt. Wenn Sie dies ändern möchten, legen Sie pagerSnapDistance für flingBehavior fest:

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

Pager-Element mit automatischer Weiterbewegung erstellen

In diesem Abschnitt wird beschrieben, wie Sie in Compose ein Pager-Element mit automatischer Weiterbewegung und Seitenindikatoren erstellen. Die Elementkollektion wird automatisch horizontal gescrollt. Nutzer können aber auch manuell zwischen den Elementen wischen. Wenn ein Nutzer mit dem Pager-Element interagiert, wird die automatische Weiterbewegung beendet.

Einfaches Beispiel

Mit den folgenden Code-Snippets wird eine einfache Pager-Implementierung mit automatischer Weiterbewegung und einem visuellen Indikator erstellt, bei der jede Seite in einer anderen Farbe gerendert wird:

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

Wichtige Informationen zum Code

  • Die Funktion AutoAdvancePager erstellt eine horizontal blätternde Ansicht mit automatischer Weiterbewegung. Sie verwendet eine Liste von Color-Objekten als Eingabe, die als Hintergrundfarben für die einzelnen Seiten verwendet werden.
  • pagerState wird mit rememberPagerState erstellt und enthält den Status des Pager-Elements.
  • pagerIsDragged und pageIsPressed verfolgen die Nutzerinteraktion.
  • Das LaunchedEffect auto-advances the pager every two seconds unless the user drags the pager or presses one of the pages.
  • HorizontalPager zeigt eine Liste von Seiten an, die jeweils ein Text-Composable enthalten, das die Seitenzahl anzeigt. Der Modifier füllt die Seite, legt die Hintergrundfarbe aus pageItems fest und macht die Seite klickbar.

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

Wichtige Informationen zum Code

  • Ein Box-Composable dient als Stammelement und enthält eine Row, um die Seitenindikatoren horizontal anzuordnen.
  • Ein benutzerdefinierter Seitenindikator wird als Reihe von Kreisen angezeigt, wobei jede Box, die auf eine CircleShape zugeschnitten ist, eine Seite darstellt.
  • Der Kreis der aktuellen Seite ist DarkGray, die anderen Kreise sind LightGray. Der Parameter currentPageIndex bestimmt, welcher Kreis dunkelgrau gerendert wird.

Ergebnis

In diesem Video wird das einfache Pager-Element mit automatischer Weiterbewegung aus den vorherigen Code-Snippets gezeigt:

Abbildung 8. Ein Pager-Element mit automatischer Weiterbewegung und einer Verzögerung von zwei Sekunden zwischen den Seitenübergängen.

Zusätzliche Ressourcen