Modyfikatory przewijania

Modyfikatory verticalScroll i horizontalScroll to najprostszy sposób na umożliwienie użytkownikowi przewijania elementu, gdy granice jego zawartości są większe niż maksymalne ograniczenia rozmiaru. Dzięki modyfikatorom verticalScroll i horizontalScroll nie musisz tłumaczyć ani przesuwać zawartości.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Prosta lista pionowa reagująca na gesty przewijania
Rysunek 1. Prosta lista pionowa reagująca na gesty przewijania.

ScrollState umożliwia zmianę pozycji przewijania lub uzyskanie jej bieżącego stanu. Aby utworzyć go z domyślnymi parametrami, użyj rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Modyfikator obszaru możliwego do przewinięcia

Modyfikator scrollableArea to podstawowy element składowy do tworzenia niestandardowych kontenerów z możliwością przewijania. Zapewnia on wyższy poziom abstrakcji niż modyfikator scrollable, obsługując typowe wymagania, takie jak interpretacja delta gestów, przycinanie treści i efekty przewijania poza zakres.

Chociaż scrollableArea jest używany w niestandardowych implementacjach, w przypadku standardowych list przewijanych lepiej jest używać gotowych rozwiązań, takich jak verticalScroll, horizontalScroll, lub funkcji kompozycyjnych, takich jak LazyColumn. Te komponenty wyższego poziomu są prostsze w typowych przypadkach użycia i są tworzone za pomocą scrollableArea.

Różnica między modyfikatorami scrollableArea i scrollable

Główna różnica między scrollableArea i scrollable polega na tym, jak interpretują gesty przewijania użytkownika:

  • scrollable (nieprzetworzona delta): delta bezpośrednio odzwierciedla fizyczny ruch danych wejściowych użytkownika (np. przeciągnięcie wskaźnika) na ekranie.
  • scrollableArea (delta zorientowana na treść): delta jest semantycznie odwrócona, aby reprezentować wybraną zmianę pozycji przewijania, dzięki czemu treść wydaje się poruszać wraz z gestem użytkownika, który zwykle jest przeciwny do ruchu wskaźnika.

Można to ująć tak: scrollable informuje o ruchu wskaźnika, a scrollableArea przekłada ten ruch na sposób, w jaki treść powinna się poruszać w typowym widoku przewijania. To odwrócenie sprawia, że scrollableArea jest bardziej naturalny podczas implementowania standardowego kontenera z możliwością przewijania.

W tabeli poniżej znajdziesz podsumowanie znaków delta w typowych scenariuszach:

Gest użytkownika

delta zgłoszona do dispatchRawDelta przez scrollable

delta zgłoszona do dispatchRawDelta przez scrollableArea*

Wskaźnik przesuwa się W GÓRĘ

Negatywne

Pozytywna

Wskaźnik przesuwa się W DÓŁ

Pozytywna

Negatywne

Wskaźnik przesuwa się W LEWO

Negatywne

Dodatnia (ujemna w przypadku języków zapisywanych od prawej do lewej)

Wskaźnik przesuwa się w PRAWO

Pozytywna

Wartość ujemna (dodatnia w przypadku języków zapisywanych od prawej do lewej)

(*) Uwaga dotycząca znaku delta scrollableArea: znak delta z scrollableArea nie jest po prostu odwrócony. Inteligentnie uwzględnia:

  1. Orientacja: pionowa lub pozioma.
  2. LayoutDirection: od lewej do prawej lub od prawej do lewej (szczególnie ważne w przypadku przewijania w poziomie).
  3. reverseScrolling flag: określa, czy kierunek przewijania jest odwrócony.

Oprócz odwrócenia różnicy przewijania scrollableArea przycina też zawartość do granic układu i obsługuje renderowanie efektów przewijania poza zakres. Domyślnie używa efektu dostarczonego przez LocalOverscrollFactory. Możesz dostosować lub wyłączyć tę funkcję, używając przeciążenia scrollableArea, które akceptuje parametr OverscrollEffect.

Kiedy używać modyfikatora scrollableArea

Modyfikatora scrollableArea należy używać, gdy trzeba utworzyć niestandardowy komponent przewijania, który nie jest odpowiednio obsługiwany przez modyfikatory horizontalScroll lub verticalScroll albo układy Lazy. Często dotyczy to przypadków, w których:

  • Niestandardowa logika układu: gdy układ elementów zmienia się dynamicznie w zależności od pozycji przewijania.
  • Unikalne efekty wizualne: stosowanie przekształceń, skalowania lub innych efektów do elementów podrzędnych podczas przewijania.
  • Bezpośrednia kontrola: potrzeba szczegółowej kontroli nad mechanizmem przewijania, która wykracza poza możliwości verticalScroll lub układów Lazy.

Twórz niestandardowe listy w formie koła za pomocą scrollableArea

Poniższy przykład pokazuje, jak za pomocą scrollableArea utworzyć niestandardową listę pionową, w której elementy zmniejszają się, gdy oddalają się od środka, co tworzy efekt wizualny „koła”. Ten rodzaj przekształcenia zależnego od przewijania jest idealnym przypadkiem użycia dla scrollableArea.

Rysunek 2. Dostosowana lista pionowa z użyciem scrollableArea.

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

Modyfikator Scrollable

Modyfikator scrollable różni się od modyfikatorów przewijania tym, że scrollable wykrywa gesty przewijania i rejestruje zmiany, ale nie przesunięcia zawartości automatycznie. Zamiast tego jest on przekazywany użytkownikowi przez ScrollableState, co jest wymagane do prawidłowego działania tego modyfikatora.

Podczas tworzenia ScrollableState musisz podać funkcję consumeScrollDelta, która będzie wywoływana przy każdym kroku przewijania (za pomocą gestu, płynnego przewijania lub szybkiego przewijania) z deltą w pikselach. Ta funkcja musi zwracać wartość odległości przewijania, aby zdarzenie było prawidłowo propagowane w przypadkach, gdy występują zagnieżdżone elementy z modyfikatorem scrollable.

Poniższy fragment kodu wykrywa gesty i wyświetla wartość liczbową przesunięcia, ale nie przesuwa żadnych elementów:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Element interfejsu wykrywający naciśnięcie palcem i wyświetlający wartość liczbową lokalizacji palca
Rysunek 3. Element interfejsu wykrywający naciśnięcie palcem i wyświetlający wartość liczbową lokalizacji palca.