Pager in der Funktion „Schreiben“

Wenn Sie Inhalte nach links und rechts oder nach oben und unten durchblättern möchten, können Sie die Composables HorizontalPager und VerticalPager verwenden. Diese Composables haben ähnliche Funktionen wie ViewPager im View-System. Standardmäßig nimmt die HorizontalPager die volle Breite des Bildschirms ein, die VerticalPager die volle Höhe und die Pager blättern jeweils nur eine Seite. Alle diese Standardeinstellungen sind konfigurierbar.

HorizontalPager

Wenn Sie einen Pager erstellen möchten, der horizontal nach links und rechts scrollt, verwenden Sie HorizontalPager:

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 einen Pager zu erstellen, der 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 Creation

Seiten in HorizontalPager und VerticalPager werden bei Bedarf verzögert zusammengestellt und gerendert. Während der Nutzer durch die Seiten scrollt, werden nicht mehr benötigte Seiten vom Composable entfernt.

Weitere Seiten im Hintergrund laden

Standardmäßig werden im Pager nur die sichtbaren Seiten auf dem Bildschirm geladen. Wenn Sie mehr Seiten im Hintergrund laden möchten, setzen Sie beyondBoundsPageCount auf einen Wert, der größer als null ist.

Zu einem Element im Pager scrollen

Wenn Sie zu einer bestimmten Seite im Pager scrollen möchten, erstellen Sie mit rememberPagerState() ein PagerState-Objekt und übergeben Sie es als state-Parameter an den Pager. In diesem Status können Sie PagerState#scrollToPage() 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 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 bei Änderungen am Seitenstatus erhalten

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

  • currentPage: Die Seite, die der Position des Snapshots am nächsten ist. Standardmäßig befindet sich die Einrastposition am Anfang des Layouts.
  • settledPage: Die Seitenzahl, wenn keine Animation oder kein Scrollen ausgeführt wird. Dies unterscheidet sich von der Eigenschaft currentPage, da currentPage sofort aktualisiert wird, wenn sich die Seite nah genug an der Einrastposition befindet. 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 jeder Seitenänderung ein Analytics-Ereignis senden möchten, können Sie Folgendes tun:

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 den benutzerdefinierten Indikator.

Wenn Sie beispielsweise eine einfache Kreis-Schaltfläche verwenden möchten, können Sie die Anzahl der Kreise wiederholen und die Farbe des Kreises basierend darauf ändern, 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 mit einem kreisförmigen Indikator unter dem Inhalt

Scroll-Effekte für Elemente 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 aktuell 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 anpassen möchten, je nachdem, wie weit sie vom Mittelpunkt entfernt sind, ändern Sie 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. Höhe ein. Sie können die Variable pageSize entweder 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 Seitengröße 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 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
    }
}

Innenabstand von Inhalten

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

Wenn Sie beispielsweise den start-Abstand festlegen, werden die Seiten am Ende ausgerichtet:

Pager mit Start-Padding, bei dem der Inhalt am Ende ausgerichtet ist

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

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

Pager mit Start- und End-Padding, in dem der Inhalt zentriert dargestellt wird

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

Wenn Sie den end-Abstand festlegen, werden die Seiten am Anfang ausgerichtet:

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

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

Sie können die Werte für top und bottom so festlegen, dass ähnliche Effekte für VerticalPager erzielt werden. 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 Standard-Composables HorizontalPager und VerticalPager geben an, wie Scrollgesten mit dem Pager funktionieren. Sie können die Standardeinstellungen wie pagerSnapDistance oder flingBehavior jedoch anpassen und ändern.

Andockdistanz

Standardmäßig legen HorizontalPager und VerticalPager die maximale Anzahl von Seiten fest, die mit einer Wischbewegung gleichzeitig gescrollt werden können. Wenn Sie das ändern möchten, legen Sie pagerSnapDistance für die 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 erstellen, der automatisch weiterblättert

In diesem Abschnitt wird beschrieben, wie Sie in Compose einen automatisch weiterlaufenden Pager mit Seitenindikatoren erstellen. Die Sammlung von Elementen wird automatisch horizontal gescrollt. Nutzer können aber auch manuell zwischen Elementen wischen. Wenn ein Nutzer mit dem Pager interagiert, wird die automatische Weiterbewegung beendet.

Einfaches Beispiel

Die folgenden Code-Snippets ergeben zusammen eine einfache Implementierung eines automatisch weiterlaufenden Pagers mit einem visuellen Indikator, bei dem 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 paginierte Ansicht mit automatischer Weiterbewegung. Als Eingabe wird eine Liste von Color-Objekten verwendet, die als Hintergrundfarben für die einzelnen Seiten dienen.
  • pagerState wird mit rememberPagerState erstellt, das den Status des Pagers enthält.
  • Mit pagerIsDragged und pageIsPressed werden Nutzerinteraktionen erfasst.
  • Die LaunchedEffect wird alle zwei Sekunden automatisch weitergeschaltet, es sei denn, der Nutzer zieht die Paginierung oder drückt auf eine der Seiten.
  • HorizontalPager zeigt eine Liste von Seiten an, die jeweils ein Text-Composable mit der Seitenzahl enthalten. Der Modifikator 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 Punkte zum Code

  • Als Stammelement wird eine Box-Composable verwendet.
    • Im Box wird mit dem Composable Row die horizontale Anordnung der Seitenindikatoren festgelegt.
  • Eine benutzerdefinierte Seitenanzeige wird als Reihe von Kreisen dargestellt, wobei jeder Box, der an ein circle angehängt 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 der grundlegende Pager mit automatischer Weiterleitung aus den vorherigen Snippets gezeigt:

Abbildung 1. Ein automatisch weiterlaufender Pager mit einer Verzögerung von zwei Sekunden zwischen den einzelnen Seitenübergängen.

Zusätzliche Ressourcen