Listy z funkcją Utwórz na Wear OS


Listy umożliwiają użytkownikom wybieranie elementów z zestawu opcji na urządzeniach z Wear OS.

Wiele urządzeń z Wear OS ma okrągłe ekrany, co utrudnia wyświetlanie elementów listy w górnej i dolnej części ekranu. Z tego powodu Compose na Wear OS zawiera wersję klasy LazyColumn o nazwie TransformingLazyColumn, która obsługuje animacje skalowania i przekształcania. Gdy elementy przesuwają się na krawędzie, zmniejszają się i znikają.

Aby zastosować zalecane efekty skalowania i przewijania:

  1. Użyj Modifier.transformedHeight, aby funkcja Compose mogła obliczyć zmianę wysokości podczas przewijania elementu na ekranie.
  2. Użyj ikony transformation = SurfaceTransformation(transformationSpec), aby zastosować efekty wizualne, w tym zmniejszyć zawartość elementu.
  3. Użyj niestandardowego TransformationSpec w przypadku komponentów, które nie przyjmują transformation jako parametru, np. Text.

Poniższa animacja pokazuje, jak element listy skaluje się i zmienia kształt, gdy zbliża się do górnej i dolnej krawędzi ekranu:

Poniższy fragment kodu pokazuje, jak utworzyć listę za pomocą układu TransformingLazyColumn, aby utworzyć treści, które świetnie wyglądają na różnych rozmiarach ekranu Wear OS.

Fragment kodu pokazuje też użycie modyfikatora minimumVerticalContentPadding, który należy ustawić w elementach listy, aby zastosować odpowiedni odstęp u góry i u dołu listy.

Aby wyświetlić wskaźnik przewijania, podziel columnState między ScreenScaffoldTransformingLazyColumn:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

Dodawanie efektu „snap-and-fling”

Przyciąganie zapewnia, że gdy użytkownik zakończy przewijanie lub gest szybkiego przesunięcia, lista zatrzyma się z elementem umieszczonym dokładnie w określonym punkcie, zwykle na środku ekranu. Na okrągłych ekranach, na których elementy skalują się i przekształcają w miarę oddalania się od środka, przyciąganie jest szczególnie przydatne, ponieważ zapewnia, że najbardziej odpowiedni element pozostaje w pełni widoczny i czytelny w optymalnym obszarze wyświetlania.

Aby dodać zachowanie przyciągania i przesuwania, ustaw parametr flingBehavior na wartość TransformingLazyColumnDefaults.snapFlingBehavior(columnState). Ustaw rotaryScrollableBehavior tak, aby pasowało do RotaryScrollableDefaults.snapBehavior(columnState), co zapewni spójne działanie podczas korzystania z fizycznego pokrętła lub ramki.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

Układ odwrócony

Domyślnie lista z możliwością przewijania jest zakotwiczona u góry. Jeśli użytkownik przewinie standardową listę do dołu i na końcu zostanie dodany nowy element, lista zachowa widok użytkownika na bieżący element. Jeśli na przykład użytkownik wyświetla element 10 u dołu ekranu, a dodany zostanie element 11, widok pozostanie skupiony na elemencie 10, a element 11 pojawi się poza ekranem poniżej bieżącego widoku.

W przypadku aplikacji do przesyłania wiadomości lub dzienników na żywo takie zachowanie jest zwykle niepożądane. Gdy pojawią się nowe elementy, użytkownicy zwykle chcą od razu zobaczyć najnowsze treści, jeśli są już na dole listy. Jeśli wiele elementów pojawi się jednocześnie, lista powinna przejść do wyświetlania najnowszego elementu na dole (co oznacza, że niektóre elementy pośrednie mogą nie być w ogóle wyświetlane, chyba że użytkownik przewinie listę w górę).

Aby obsługiwać te przypadki użycia, TransformingLazyColumn umożliwia odwrócenie układu przez ustawienie reverseLayout = true. Spowoduje to zmianę punktu zakotwiczenia listy z górnej krawędzi na dolną.

Ustawienie reverseLayout = true odwraca też kolejność wizualną elementów i kierunek gestów przewijania:

  • Elementy są ułożone od dołu do góry, co oznacza, że indeks 0 znajduje się u dołu ekranu.
  • Przewijanie w górę powoduje wyświetlanie elementów o wyższych indeksach.

Aby dodać zachowanie przyciągania i przesuwania wraz z odwrotnym układem, możesz połączyć atrybuty flingBehaviorrotaryScrollableBehavior, jak pokazano w tym fragmencie kodu:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

Poniższe obrazy pokazują różnicę między zwykłą listą a listą odwróconą:

TransformingLazyColumn z normalnym układem, w którym na górze znajduje się element 1, a pozostałe elementy są ułożone w kolejności rosnącej.
Rysunek 1. Standardowy układ listy, w którym treść wypełnia obszar od góry do dołu.
TransformingLazyColumn z odwróconym układem, w którym element 1 jest u dołu, a elementy są ułożone w kolejności malejącej w kierunku góry.
Rysunek 2. Odwrócony układ listy, w którym treść wypełnia się od dołu do góry.

Przyciski na krawędziach list

W Material 3 możesz dodać EdgeButton, czyli przycisk przylegający do krawędzi u dołu list. Nie dodawaj go jednak jako elementu w TransformingLazyColumn, ale użyj zamiast tego gniazda edgeButtonScreenScaffold.

Użycie boksu edgeButton zapewnia prawidłowe umieszczenie przycisku u dołu ekranu i odpowiednie działanie podczas przewijania listy.

Poniższy fragment kodu pokazuje, jak wdrożyć EdgeButton:

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

Funkcja SwipeToReveal na listach

Komponent SwipeToReveal umożliwia dostęp do działań dotyczących elementu listy, takich jak Card lub Chip, poprzez przesunięcie. Przesunięcie zwykle powoduje wyświetlenie z boku 1 lub 2 przycisków działania (np. „Usuń” lub „Więcej”).

Gdy używasz elementu SwipeToReveal w ramach elementu TransformingLazyColumn, postępuj zgodnie z tymi wskazówkami:

  • Resetuj podczas przewijania: gdy użytkownik przewinie listę, zresetuj wszystkie otwarte elementy do stanu zakrytego.
  • Spójna wysokość: ustaw wysokość przycisków polecenia tak, aby pasowała do wewnętrznego przesuniętego elementu (niezależnie od tego, czy jest to Button czy Card), aby zapewnić spójny wygląd.
  • Przekształć kontener: zastosuj modyfikator transformedHeighttransformationSpec do samego komponentu SwipeToReveal.
  • Nie stosuj podwójnych przekształceń: nie używaj modyfikatorów transformedHeight ani transformation w przypadku wewnętrznego elementu przesuwanego (karty lub przycisku w kontenerze SwipeToReveal).