Pager in der Funktion „Schreiben“

Wenn du horizontal oder vertikal durch Inhalte blättern möchtest, kannst du die Composables HorizontalPager und VerticalPager verwenden. Diese haben ähnliche Funktionen wie ViewPager im Ansichtssystem. Standardmäßig nimmt HorizontalPager die gesamte Bildschirmbreite und VerticalPager die gesamte Höhe ein. Außerdem wird jeweils nur eine Seite umgeblättert. Alle diese Standardeinstellungen sind konfigurierbar.

HorizontalPager

Verwenden Sie HorizontalPager, um einen Pager zu erstellen, der 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

Wenn Sie einen Pager erstellen möchten, der nach oben und unten scrollt, verwenden Sie VerticalPager:

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 Creation

Seiten in HorizontalPager und VerticalPager werden verzögert zusammengestellt und bei Bedarf angeordnet. Wenn der Nutzer durch die Seiten scrollt, entfernt die komponierbare Funktion alle Seiten, die nicht mehr benötigt werden.

Weitere Seiten außerhalb des Bildschirms laden

Standardmäßig lädt der Pager nur die sichtbaren Seiten auf dem Bildschirm. Wenn Sie mehr Seiten außerhalb des Bildschirms laden möchten, legen Sie für beyondBoundsPageCount einen Wert größer als null fest.

Zu einem Element im Pager scrollen

Wenn Sie im Pager zu einer bestimmten Seite scrollen möchten, erstellen Sie mit rememberPagerState() ein PagerState-Objekt und übergeben Sie es als state-Parameter an den Pager. Sie können PagerState#scrollToPage() für diesen Status innerhalb einer 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 die Seite animieren möchten, verwenden Sie die Funktion 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")
}

Benachrichtigungen zu Änderungen des Seitenstatus erhalten

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

  • currentPage: Die Seite, die der Einrastposition am nächsten ist. Standardmäßig befindet sich die Einrastposition am Anfang des Layouts.
  • settledPage: Die Seitennummer, wenn keine Animation oder kein Scrollen ausgeführt wird. Dies unterscheidet sich von der Property currentPage, da currentPage sofort aktualisiert wird, wenn die Seite nahe genug an der Einrastposition ist. settledPage bleibt jedoch unverändert, bis alle Animationen abgeschlossen sind.
  • targetPage: Die vorgeschlagene Stoppposition für eine Scrollbewegung.

Mit der Funktion snapshotFlow können Sie Änderungen an diesen Variablen beobachten und darauf reagieren. Wenn Sie beispielsweise bei jedem Seitenwechsel 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")
}

Seitenanzeige hinzufügen

Wenn du einer Seite einen Indikator hinzufügen möchtest, verwende das PagerState-Objekt, um Informationen darüber zu erhalten, welche Seite aus der Anzahl der Seiten ausgewählt ist, und zeichne deinen benutzerdefinierten Indikator.

Wenn Sie beispielsweise einen Kreisindikator erstellen möchten, können Sie die Anzahl der Kreise wiederholen und die Kreisfarbe je nachdem, ob die Seite ausgewählt ist, mit pagerState.currentPage ändern:

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 einer Kreisanzeige unter dem Inhalt
Abbildung 3: Pager mit einer Kreisanzeige unter dem Inhalt

Scrolleffekte für Elemente auf Inhalte anwenden

Ein häufiger Anwendungsfall ist die Verwendung der Scrollposition, 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 Transformationseffekte auf Ihre Inhalte anwenden, die auf der Entfernung von der ausgewählten Seite basieren.

Abbildung 4: Transformationen auf Pager-Inhalte anwenden

Wenn Sie beispielsweise die Deckkraft von Elementen basierend auf ihrer Entfernung von der Mitte anpassen möchten, ändern Sie die alpha mit Modifier.graphicsLayer für ein Element im 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
    }
}

Benutzerdefinierte Seitengrößen

Standardmäßig nehmen HorizontalPager und VerticalPager die volle Breite bzw. die volle Höhe ein. Sie können die Variable pageSize entweder auf Fixed, Fill (Standard) oder auf 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 Seiten anhand der 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
    }
}

Inhaltsabstand

Sowohl HorizontalPager als auch VerticalPager unterstützen das Ändern des Innenabstands, sodass Sie die maximale Größe und Ausrichtung von Seiten beeinflussen können.

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

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

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

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

Pager mit Padding am Anfang und Ende, der den Inhalt zentriert anzeigt
Abbildung 6: Pager mit horizontalem Padding.

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

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

Pager mit Start- und End-Padding, bei dem der Inhalt am Anfang ausgerichtet ist
Abbildung 7. Pager 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 jede der Padding-Dimensionen auf einen beliebigen Wert festlegen.

Scrollverhalten anpassen

Die standardmäßigen Compose-Funktionen HorizontalPager und VerticalPager geben an, wie Scroll-Gesten mit dem Pager funktionieren. Sie können die Standardeinstellungen wie pagerSnapDistance oder flingBehavior jedoch anpassen und ändern.

Andockentfernung

Standardmäßig legen HorizontalPager und VerticalPager die maximale Anzahl von Seiten, die mit einer Wischgeste übersprungen werden können, auf jeweils eine Seite fest. 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 mit automatischem Übergang erstellen

In diesem Abschnitt wird beschrieben, wie Sie in Compose einen automatisch weiterblätternden Pager mit Seitenanzeigen erstellen. Die Sammlung von Elementen wird automatisch horizontal gescrollt, Nutzer können aber auch manuell zwischen den Elementen wischen. Wenn ein Nutzer mit dem Pager interagiert, wird der automatische Fortschritt beendet.

Einfaches Beispiel

Zusammen erstellen die folgenden Snippets eine einfache Implementierung eines automatischen Seitenwechsels mit einer visuellen Anzeige, 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 Punkte zum Code

  • Die Funktion AutoAdvancePager erstellt eine horizontal scrollbare Ansicht mit automatischem Fortschritt. Als Eingabe wird eine Liste von Color-Objekten verwendet, die als Hintergrundfarben für jede Seite verwendet werden.
  • pagerState wird mit rememberPagerState erstellt, das den Status des Pagers enthält.
  • pagerIsDragged und pageIsPressed erfassen die Nutzerinteraktion.
  • Der LaunchedEffect wechselt alle zwei Sekunden automatisch zur nächsten Seite, es sei denn, der Nutzer zieht den Pager oder drückt auf eine der Seiten.
  • HorizontalPager zeigt eine Liste von Seiten an, die jeweils ein Text-Composable mit der Seitennummer enthalten. Der Modifikator füllt die Seite, legt die Hintergrundfarbe aus pageItems fest und macht die Seite anklickbar.

@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 Punkte zum Code

  • Ein Box-Composable fungiert als Stammelement und enthält eine Row, um die Seitenanzeigen horizontal anzuordnen.
  • Ein benutzerdefinierter Seitenindikator wird als eine Reihe von Kreisen angezeigt, wobei jeder Box, der auf einen 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 dargestellt wird.

Ergebnis

In diesem Video wird der einfache Pager mit automatischem Übergang aus den vorherigen Snippets gezeigt:

Abbildung 8: Ein automatischer Übergang mit einer Verzögerung von zwei Sekunden zwischen den einzelnen Seiten.

Zusätzliche Ressourcen