Nakładanie warstw architektury w Jetpack Compose

Ta strona zawiera ogólny przegląd warstw architektury, z których składa się Jetpack Compose, oraz podstawowych zasad, które determinują ten projekt.

Jetpack Compose nie jest pojedynczym monolitycznym projektem. Jest tworzony z liczby modułów, które są łączone ze sobą, aby utworzyć kompletny pakiet. Poznanie różnych modułów tworzących Jetpack Compose pozwala:

  • Używaj odpowiedniego poziomu abstrakcji do tworzenia aplikacji lub biblioteki
  • Dowiedz się, kiedy możesz „zejść” na niższy poziom, aby uzyskać większą kontrolę lub możliwość dostosowania
  • Zminimalizuj zależności

Warstwy

Główne warstwy Jetpack Compose to:

Rysunek 1. Główne warstwy Jetpack Compose.

Każda warstwa jest budowana na niższych poziomach, łącząc funkcje w komponenty wyższego poziomu. Każda warstwa opiera się na publicznych interfejsach API niższych warstw, aby weryfikować granice modułu i umożliwiać zastępowanie dowolnej warstwy w razie potrzeby. Przyjrzyjmy się tym warstwom od dołu do góry.

Czas działania
Ten moduł zawiera podstawy dotyczące środowiska wykonawczego Compose, takie jak remember, mutableStateOf, @Composableoraz adnotacjeSideEffect. Jeśli potrzebujesz tylko funkcji zarządzania drzewem w Compose, a nie interfejsu użytkownika, możesz tworzyć bezpośrednio na tej warstwie.
Interfejs użytkownika
Warstwa interfejsu składa się z kilku modułów (ui-text, ui-graphics, ui-tooling itp.). Te moduły implementują podstawy pakietu narzędzi interfejsu użytkownika, takie jak LayoutNode, Modifier, moduły obsługi danych wejściowych, niestandardowe układy i rysowanie. Jeśli potrzebujesz tylko podstawowych koncepcji dotyczących pakietu narzędzi interfejsu użytkownika, możesz zbudować tę warstwę.
Fundamenty
Ten moduł zawiera elementy konstrukcyjne interfejsu Compose niezależne od systemu projektowania, takie jak Row i Column, LazyColumn, rozpoznawanie konkretnych gestów itp. Możesz tworzyć własne elementy na podstawie warstwy podstawowej, aby tworzyć własny system projektowania.
Materiał
Ten moduł zapewnia implementację systemu Material Design dla interfejsu Compose, oferując system motywów, stylizowane komponenty, animacje fali i ikony. Używaj tej warstwy, gdy korzystasz z Material Design w aplikacji.

Zasady projektowania

W Jetpack Compose warto pamiętać, by udostępniać małe, precyzyjne elementy, które można połączyć (lub skomponować) razem, zamiast korzystać z kilku monolitycznych komponentów. Takie podejście ma wiele zalet.

Kontrola

Komponenty wyższego poziomu zwykle robią więcej, ale ograniczają Twoją bezpośrednią kontrolę. Jeśli potrzebujesz większej kontroli, możesz „rozwinąć” komponent na niższym poziomie.

Jeśli na przykład chcesz animować kolor komponentu, możesz użyć interfejsu animateColorAsState:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

Jeśli jednak chcesz, aby komponent zawsze był szary, nie możesz tego zrobić za pomocą tego interfejsu API. Zamiast tego możesz otworzyć menu i użyć interfejsu API o niższym poziomie Animatable:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

Interfejs API animateColorAsState wyższego poziomu jest zbudowany na interfejsie API Animatable niższego poziomu. Korzystanie z interfejsu API niższego poziomu jest bardziej złożone, ale daje większą kontrolę. Wybierz poziom abstrakcji, który najlepiej odpowiada Twoim potrzebom.

Dostosowywanie

W przypadku tworzenia komponentów wyższego poziomu z mniejszych elementów znacznie łatwiej jest je w razie potrzeby dostosować. Przyjrzyjmy się np. implementacji właściwości Button dostępnej w warstwie Material Design:

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Button składa się z 4 komponentów:

  1. MateriałSurfacez tłem, kształtem, obsługą kliknięć itp.

  2. CompositionLocalProvider, który zmienia wersję alfa treści po włączeniu lub wyłączeniu przycisku

  3. ProvideTextStyle ustawia domyślny styl tekstu, którego chcesz używać

  4. Element Row określa domyślną zasadę układu treści przycisku.

Aby ułatwić zrozumienie struktury, pominęliśmy niektóre parametry i komentarze, ale cały komponent składa się z zaledwie około 40 wierszy kodu, ponieważ po prostu łączy te 4 elementy, aby zaimplementować przycisk. Komponenty takie jak Button mają określone parametry, które udostępniają, aby zachować równowagę między umożliwieniem korzystania z częstych dostosowań a niekontrolowanym wzrostem liczby parametrów, który może utrudnić korzystanie z komponentu. Komponenty Material Design oferują np. opcje dostosowywania określone w systemie Material Design, dzięki czemu łatwo jest przestrzegać zasad tego interfejsu.

Jeśli jednak chcesz wprowadzić zmiany, które wykraczają poza parametry komponentu, możesz „zejść” o poziom niżej i stworzyć jego wersję. Na przykład atrybut Material Design określa, że tło przycisków powinno mieć jednolity kolor. Jeśli potrzebujesz tła z gradientem, parametry Button nie obsługują tej opcji. W tym przypadku możesz użyć implementacji Material Button jako odniesienia i utworzyć własny komponent:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

W powyższej implementacji nadal są używane komponenty z warstwy Materiał, takie jak koncepcja Materiału w wersji alfa oraz bieżący styl tekstu. Jednak zastępuje materiał Surface materiałem Row i stylizuje go, aby uzyskać pożądany wygląd.

Jeśli nie chcesz w ogóle używać koncepcji Material Design, np. podczas tworzenia własnego systemu projektowania, możesz ograniczyć się do korzystania tylko z elementów warstwy podstawowej:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose rezerwuje najprostsze nazwy dla komponentów najwyższego poziomu. Na przykład androidx.compose.material.Text opiera się na androidx.compose.foundation.text.BasicText. Dzięki temu możesz podać własną implementację z najbardziej rozpoznawalną nazwą, jeśli chcesz zastąpić wyższe poziomy.

Wybór odpowiedniego poziomu abstrakcji

Filozofia tworzenia warstwowych, wielokrotnego użytku komponentów w Compose oznacza, że nie zawsze musisz sięgać po elementy niższego poziomu. Wiele komponentów wyższego poziomu nie tylko oferuje więcej funkcji, ale często implementuje też najlepsze praktyki, takie jak obsługa ułatwień dostępu.

Jeśli na przykład chcesz dodać do niestandardowego komponentu obsługę gestów, możesz go utworzyć od podstaw za pomocą komponentu Modifier.pointerInput, ale istnieją też inne komponenty wyższego poziomu, które mogą stanowić lepszy punkt wyjścia, np. Modifier.draggable, Modifier.scrollable lub Modifier.swipeable.

Zazwyczaj lepiej jest tworzyć komponenty na najwyższym poziomie, które oferują funkcje potrzebne do korzystania ze sprawdzonych metod.

Więcej informacji

Przykładowy projekt systemu niestandardowego znajdziesz w pliku Jetsnack.

Obecnie nie ma rekomendacji.

na swoje konto Google.