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 podstawowe zasady, na których opiera się ten projekt.

Jetpack Compose nie jest pojedynczym projektem monolitycznym – składa się z kilku modułów, które są połączone razem w całości. Zrozumienie różnych modułów, które składają się na Jetpack Compose, pozwala:

  • Przy tworzeniu aplikacji lub biblioteki używaj odpowiedniego poziomu abstrakcji
  • Dowiedz się, kiedy możesz „zejść” na niższy poziom, by zyskać większą kontrolę lub dostosowanie.
  • Minimalizuj zależności

Warstwy

Główne warstwy gry Jetpack Compose to:

Rysunek 1. Główne warstwy Jetpack Compose.

Każda warstwa jest tworzona na niższych poziomach i łączy różne funkcje, aby tworzyć komponenty wyższej jakości. Każda warstwa korzysta z publicznych interfejsów API dolnych warstw, aby zweryfikować granice modułów i w razie potrzeby zastąpić dowolną warstwę. Przyjrzyjmy się tym warstwom od dołu do góry.

Środowisko wykonawcze
W tym module poznasz podstawowe informacje o środowisku wykonawczym tworzenia wiadomości, takie jak remember, mutableStateOf, adnotacje @Composable i SideEffect. Jeśli potrzebujesz umiejętności zarządzania drzewami w komponencie, a nie interfejsu użytkownika, możesz rozważyć tworzenie go bezpośrednio na tej warstwie.
Interfejs użytkownika
Warstwa interfejsu składa się z wielu modułów (ui-text, ui-graphics, ui-tooling itp.). Moduły te implementują podstawowe narzędzia interfejsu, takie jak LayoutNode, Modifier, moduły obsługi wprowadzania danych, układy niestandardowe i rysunki. Możesz skorzystać z tej warstwy, jeśli potrzebujesz tylko podstawowych koncepcji zestawu narzędzi interfejsu.
Podstawa
Ten moduł zawiera niezależne od systemu projektowania elementy interfejsu użytkownika Compose, takie jak Row, Column, LazyColumn, rozpoznawanie konkretnych gestów itp. Możesz też stworzyć własny system projektowania oparty na warstwie podstawowej.
Materiał
W tym module omówiono wdrożenie systemu Material Design UI w interfejsie Compose, w tym system tworzenia motywów, elementy stylu, wskaźniki fali oraz ikony. Wykorzystaj tę warstwę, korzystając z interfejsu Material Design w swojej aplikacji.

Zasady dotyczące projektowania

Główną zasadą w Jetpack Compose jest udostępnianie małych, wyspecjalizowanych elementów, które można połączyć (lub skomponować) razem, a nie jako kilka monolitycznych elementów. Takie podejście ma wiele zalet.

Sterowanie

Komponenty o wyższym poziomie zwykle działają lepiej, ale ograniczają Twoją bezpośrednią kontrolę. Jeśli potrzebujesz większej kontroli, możesz użyć komponentu niższego poziomu.

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

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

Jeśli jednak komponent ma zawsze mieć kolor szary, nie możesz tego zrobić za pomocą tego interfejsu API. Możesz ją rozwinąć, aby użyć interfejsu API Animatable niższego poziomu:

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

Interfejs animateColorAsState API wyższego poziomu jest utworzony na podstawie interfejsu Animatable API 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.

Personalizacja

Połączenie komponentów wyższego poziomu z mniejszych elementów znacznie ułatwia dostosowywanie komponentów. Spójrzmy na przykład na implementację komponentu Button za pośrednictwem warstwy Material:

@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ł Surface, który określa tło, kształt, 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óry będzie używany

  4. Row określa domyślne zasady układu treści przycisku.

Pominęliśmy niektóre parametry i komentarze, aby zwiększyć przejrzystość struktury, ale cały komponent składa się tylko z 40 wierszy kodu, ponieważ po prostu łączy te 4 komponenty w celu implementacji przycisku. Komponenty takie jak Button oceniają, jakie parametry są udostępniane. Pozwala to znaleźć równowagę między umożliwianiem typowych dostosowań a eksplozją parametrów, które mogą utrudniać korzystanie z danego komponentu. Na przykład komponenty Material Design oferują możliwość dostosowania określonego w systemie Material Design, aby ułatwić przestrzeganie zasad Material Design.

Jeśli jednak chcesz dostosować komponent poza parametrami, możesz „zpuścić” poziom i utworzyć rozwidlenie komponentu. Na przykład styl Material Design określa, że przyciski powinny mieć jednolite tło. Jeśli potrzebujesz tła w postaci gradientu, parametry Button nie obsługują tej opcji. W takim przypadku możesz jako odniesienia użyć implementacji Material Button 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 używa się komponentów z warstwy Material, takich jak koncepcja obecnej wersji alfa interfejsu Material Design i bieżący styl tekstu. Zastępuje jednak materiał Surface elementem Row i nadaje mu styl, aby uzyskać odpowiedni wygląd.

Jeśli nie chcesz używać Material Concepts, np. tworzysz własny system projektowania dostosowanego do indywidualnych potrzeb, możesz z kolei skorzystać z komponentó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 zastrzega najprostsze nazwy 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 nadać własną implementację o największej wykrywalnej nazwie, jeśli chcesz zastąpić wyższe poziomy.

Wybieranie odpowiedniej abstrakcji

Przyjęta przez nas filozofia tworzenia warstwowych komponentów wielokrotnego użytku oznacza, że nie zawsze należy sięgać po elementy składowe niższego poziomu. Wiele komponentów wyższego poziomu nie tylko zapewnia większą funkcjonalność, ale często stosuje sprawdzone metody, takie jak obsługa ułatwień dostępu.

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

Najlepiej jest korzystać z komponentu najwyższego poziomu, który obejmuje funkcje potrzebne do korzystania z uwzględnionych sprawdzonych metod.

Więcej informacji

Aby dowiedzieć się, jak utworzyć własny system do projektowania, zapoznaj się z przykładem Jeetsnack.