Tworzenie

Jetpack Compose to nowoczesny pakiet narzędzi deklaratywnych UI na Androida. Tworzenie wiadomości ułatwisz pisanie i obsługę interfejsu aplikacji dzięki deklaratywnemu interfejsowi API, który pozwala renderować interfejs aplikacji bez konieczności bezterminowej mutacji frontendu wyświetleń. Terminologia ta wymaga wyjaśnienia, ale jej konsekwencje są bardzo ważne podczas projektowania aplikacji.

paradygmat programowania deklaratywnego

Dawniej hierarchię widoków Androida można było przedstawić jako drzewo interfejsu użytkownika. widżety. Gdy stan aplikacji zmienia się na przykład z powodu czynników takich jak użytkownik interakcji, należy zaktualizować hierarchię interfejsu, aby wyświetlić bieżące dane. Najczęstszym sposobem aktualizowania UI jest chodzenie po drzewie przy użyciu funkcji takich jak: findViewById() oraz zmień węzły, wywołując metody takie jak button.setText(String), container.addChild(View), czy img.setImageBitmap(Bitmap). Metody te zmienić wewnętrzny stan widżetu.

Ręczne manipulowanie widokami zwiększa prawdopodobieństwo wystąpienia błędów. Jeśli dane są renderowane w wielu miejsc, łatwo zapomnieć zaktualizować jeden z widoków, w których to się pojawia. Łatwo też utworzyć stany niezgodne z prawem, jeśli dwie aktualizacje powodują konflikt w nieoczekiwany sposób. Aktualizacja może na przykład próbować ustawić wartość węzła, który została właśnie usunięta z interfejsu. Ogólnie złożoność konserwacji oprogramowania rośnie wraz z liczbą wyświetleń, które wymagają aktualizacji.

W ciągu ostatnich kilku lat cała branża zaczęła przechodzić deklaratywny model UI, co znacznie upraszcza pracę inżynierską tworzenia i aktualizowania interfejsów. Metoda ta opiera się na koncepcjach ponownie wygenerować cały ekran od zera, a potem zastosować tylko niezbędne zmian. Takie podejście pozwala uniknąć złożoności ręcznej aktualizacji stanu hierarchię widoków. Compose to deklaratywna platforma interfejsu użytkownika.

Jednym z problemów związanych z ponownym generowaniem całego ekranu jest to, pod względem czasu, mocy obliczeniowej i zużywania baterii. Aby złagodzić kosztów, funkcja Compose inteligentnie określa, które części interfejsu i zmieniane w danym momencie. Ma to wpływ na projektowanie komponentów interfejsu, jak omówiliśmy w sekcji Ponowna kompozycja.

Prosta funkcja kompozycyjna

Za pomocą tej funkcji możesz utworzyć interfejs, definiując zestaw funkcji composable, które przyjmują dane i generują elementy interfejsu użytkownika. Prosty przykład to widżet Greeting, który pobiera String i wyświetla widżet Text który wyświetla wiadomość powitalną.

Zrzut ekranu telefonu z widocznym tekstem

Rysunek 1. Prosta funkcja kompozycyjna, która przekazuje dane i wykorzystuje je do ani renderowania widżetu tekstowego na ekranie.

Kilka ważnych informacji o tej funkcji:

  • Do funkcji dodano adnotację @Composable. Wszystkie kompozycyjne funkcje muszą mieć tę adnotację; ta adnotacja informuje kompilatora, że ta funkcja ma konwertować dane na interfejs użytkownika.

  • Funkcja pobiera dane. Funkcje kompozycyjne mogą akceptować parametry, które pozwalają logice aplikacji na opisanie UI. W tym przypadku widżet akceptuje obiekt String, aby powitać użytkownika po imieniu.

  • Funkcja wyświetla tekst w interfejsie. Robi to przez wywołanie funkcji Text() funkcja kompozycyjna, która tworzy tekstowy element interfejsu użytkownika. Składane funkcje generują hierarchię UI przez wywoływanie innych funkcji kompozycyjnych.

  • Funkcja nie zwraca niczego. Funkcje tworzenia, które generują interfejs użytkownika, nie zwracają dowolną wartość, ponieważ opisują pożądany stan ekranu. zamiast tworzyć widżety interfejsu.

  • Ta funkcja jest szybka, idempotent, i wolne od efektów ubocznych.

    • Funkcja działa tak samo w przypadku wielokrotnego wywoływania z tego samego argumentu i nie używa w nim innych wartości, takich jak zmienne globalne lub połączenia z numerem random().
    • Funkcja opisuje interfejs użytkownika bez żadnych efektów ubocznych, takich jak: właściwości lub zmiennych globalnych.

    Ogólnie wszystkie funkcje kompozycyjne powinny być zapisywane za pomocą tych z powodów omówionych w sekcji Zmiana kompozycji.

Deklaratywna zmiana paradygmatu

Korzystanie z wielu niezbędnych narzędzi UI zorientowanych na obiekt wymaga inicjowania interfejsu, tworząc drzewo widżetów. Zwykle robi się to poprzez zwiększanie zakresu układu XML. . Każdy widżet zachowuje własny stan wewnętrzny i udostępnia metody pobierające i metody ustawiania, które pozwalają logice aplikacji na interakcję z widżetem.

W metodzie deklaratywnej w usłudze Compose widżety są stosunkowo bezstanowe i nie ujawniaj funkcji ustawiających ani pobierających. widżety nie są wyświetlane jako obiekty. Aby zaktualizować interfejs użytkownika, wywołaj metodę ta sama funkcja kompozycyjna z różnymi argumentami. Ułatwia to podawanie na wzorce architektoniczne, takie jak ViewModel, jak opisano w Przewodnik po architekturze aplikacji. Twoje elementy kompozycyjne odpowiada za przekształcanie bieżącego stanu aplikacji w UI za każdym razem które są aktualizowane.

Ilustracja przepływu danych w interfejsie tworzenia wiadomości – od obiektów wysokiego poziomu aż do ich elementów podrzędnych.

Rysunek 2. Logika aplikacji dostarcza dane do funkcji kompozycyjnej najwyższego poziomu. Funkcja ta wykorzystuje dane do opisania UI przez wywoływanie innych elementów kompozycyjnych, przekazuje odpowiednie dane do tych elementów kompozycyjnych i w dalszej części hierarchii.

Gdy użytkownik wejdzie w interakcję z interfejsem, wywoła on zdarzenia takie jak onClick. Te zdarzenia powinny powiadamiać logikę aplikacji, która może potem zmienić jej stan. Gdy stan się zmieni, funkcje kompozycyjne są wywoływane ponownie z nowym argumentem i skalowalnych danych. Elementy interfejsu są ponownie rysowane. Ten proces nazywamy zmianę kompozycji.

Ilustracja przedstawiająca, jak elementy interfejsu reagują na interakcję, wywołując zdarzenia obsługiwane przez logikę aplikacji.

Rysunek 3. Użytkownik wszedł w interakcję z elementem interfejsu, powodując wystąpienie zdarzenia . Logika aplikacji reaguje na zdarzenie, a następnie funkcje kompozycyjne w razie potrzeby są automatycznie wywoływane ponownie z nowymi parametrami.

Zawartość dynamiczna

Ponieważ funkcje kompozycyjne są napisane w Kotlin, a nie w języku XML, można je tak dynamiczny jak każdy inny kod Kotlin. Załóżmy, że chcesz utworzyć interfejs użytkownika witający listę użytkowników:

@Composable
fun Greeting(names: List<String>) {
    for (name in names) {
        Text("Hello $name")
    }
}

Ta funkcja pobiera listę imion i generuje powitanie dla każdego użytkownika. Funkcje kompozycyjne bywają dość zaawansowane. Za pomocą instrukcji if możesz: zdecyduj, czy chcesz wyświetlać konkretny element interfejsu. Możesz użyć pętli. Dostępne opcje funkcji pomocniczych. Mają Państwo pełną swobodę język. Ta moc i elastyczność to główne zalety Jetpacka. Utwórz.

Zmiana kompozycji

Aby zmienić widżet w imperatywnym modelu interfejsu, musisz wywołać w widżecie funkcję ustawiającą. aby zmienić jej stan wewnętrzny. W funkcji tworzenia wywołujesz ponownie funkcję kompozycji. z nowymi danymi. Powoduje to ponowne skomponowanie funkcji – emitowane przez tę funkcję są w razie potrzeby ponownie pobierane z nowymi danymi. Tworzenie wiadomości może inteligentnie ponownie komponować tylko te komponenty, które zostały zmienione.

Weźmy na przykład tę funkcję kompozycyjną, która wyświetla przycisk:

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("I've been clicked $clicks times")
    }
}

Po każdym kliknięciu przycisku aplikacja wywołująca aktualizuje wartość clicks. Narzędzie Compose ponownie wywołuje funkcję lambda z funkcją Text, aby wyświetlić nową wartość. ten proces nazywamy rekompozycją. Inne funkcje, które nie zależą od nie są tworzone ponownie.

Jak już wspominaliśmy, ponowne skompilowanie całego drzewa UI może być który zużywa moc obliczeniową i baterię. Funkcja tworzenia wiadomości rozwiązuje ten problem tej inteligentnej zmiany kompozycji.

Zmiana kompozycji to proces ponownego wywoływania funkcji kompozycyjnych, gdy zmiana danych wejściowych. Dzieje się tak, gdy zmienią się dane wejściowe funkcji. Podczas tworzenia ponownie komponuje na podstawie nowych danych wejściowych, wywołuje tylko funkcje lub parametry lambda, które mógł się zmienić i pomija resztę. Przez pominięcie wszystkich funkcji lub parametrów lambda które nie mają zmienionych parametrów, umożliwia sprawne ponowne kompilowanie treści.

Nigdy nie polegają na efektach ubocznych wywoływanych przez funkcje kompozycyjne, może zostać pominięte. Jeśli to zrobisz, użytkownicy mogą zobaczyć dziwne treści, i nieprzewidywalne zachowanie w aplikacji. Efektem ubocznym jest: widoczna dla reszty aplikacji. Na przykład te działania to niebezpieczne efekty uboczne:

  • Zapisywanie we właściwości udostępnionego obiektu
  • Aktualizuję element do obserwacji w: ViewModel
  • Aktualizuję wspólne ustawienia

Funkcje kompozycyjne mogą być wykonywane ponownie tak często jak każda klatka, na przykład animacja jest renderowana. Funkcje kompozycyjne powinny być szybkie, aby unikać nie zacinają się podczas animacji. Jeśli musisz wykonać drogie operacje, takie jak odczytywanie ze wspólnych preferencji, zrób to w tle i przekaż wartość do funkcji kompozycyjnej jako parametru.

Na przykład ten kod tworzy funkcję kompozycyjną, która aktualizuje wartość w argumencie SharedPreferences Funkcja kompozycyjna nie powinna odczytywać ani zapisywać danych z udostępnionych ustawień. Zamiast tego ten kod przenosi odczyt i zapis do sekcji ViewModel w współpracy w tle. Logika aplikacji przekazuje bieżącą wartość za pomocą wywołanie zwrotne, aby aktywować aktualizację.

@Composable
fun SharedPrefsToggle(
    text: String,
    value: Boolean,
    onValueChanged: (Boolean) -> Unit
) {
    Row {
        Text(text)
        Checkbox(checked = value, onCheckedChange = onValueChanged)
    }
}

W tym dokumencie omówiono kilka kwestii, o których należy pamiętać podczas korzystania z funkcji Utwórz:

  • Funkcje kompozycyjne mogą być wykonywane w dowolnej kolejności.
  • Funkcje kompozycyjne mogą być wykonywane równolegle.
  • Zmiana kompozycji pomija jak najwięcej funkcji kompozycyjnych i lambda.
  • Zmiana kompozycji jest optymalna i może zostać anulowana.
  • Funkcja kompozycyjna może być uruchamiana dość często, tak samo jak każda klatka animacji.

W kolejnych sekcjach dowiesz się, jak tworzyć funkcje kompozycyjne do obsługi zmianę kompozycji. W każdym przypadku najlepiej jest zadbać o to, aby pliki kompozycyjne działają szybko, idempotentnie i bez efektów ubocznych.

Funkcje kompozycyjne mogą być wykonywane w dowolnej kolejności

Patrząc na kod funkcji kompozycyjnej, możesz założyć, że jest uruchamiany w kolejności, w jakiej jest. Nie musi to być jednak prawdą. Jeśli funkcja kompozycyjna zawiera wywołania innych funkcji kompozycyjnych, takich jak funkcje mogą działać w dowolnej kolejności. Tworzenie wiadomości może rozpoznać, że niektóre elementy interfejsu mają wyższy priorytet niż inne i rysują je jako pierwsze.

Załóżmy na przykład, że masz kod podobny do tego do rysowania trzech ekranów na karcie układ:

@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}

Połączenia z numerami StartScreen, MiddleScreen i EndScreen mogą być wykonywane w zamówienie. Oznacza to, że nie można na przykład ustawić w StartScreen() niektórych usług globalnych (efekt uboczny) i MiddleScreen() wykorzystać tę wartość. . Każda z tych funkcji musi być niezależna.

Funkcje kompozycyjne mogą działać równolegle

Tworzenie może zoptymalizować zmianę kompozycji, uruchamiając funkcje kompozycyjne równolegle. Dzięki temu funkcja Compose może korzystać z wielu rdzeni i uruchamiać funkcje kompozycyjne nie są widoczne na ekranie z niższym priorytetem.

Ta optymalizacja oznacza, że funkcja kompozycyjna może być wykonywana w puli wątkach w tle. Jeśli funkcja kompozycyjna wywołuje funkcję na elemencie ViewModel, Narzędzie Compose może wywołać tę funkcję z kilku wątków jednocześnie.

Aby zapewnić prawidłowe działanie aplikacji, wszystkie funkcje kompozycyjne powinny nie mają żadnych skutków ubocznych. Zamiast tego wywołują efekty uboczne z wywołań zwrotnych, takie jak: onClick, które zawsze są wykonywane w wątku UI.

Gdy funkcja kompozycyjna jest wywoływana, może ona wystąpić w innym miejscu rozmówcę. Oznacza to kod, który modyfikuje zmienne w funkcji kompozycyjnej Należy unikać lambda – zarówno dlatego, że taki kod nie jest bezpieczny, jak i ponieważ jest to niedozwolony efekt uboczny funkcji kompozycyjnej lambda.

Oto przykład funkcji kompozycyjnej, która wyświetla listę i liczbę jej elementów:

@Composable
fun ListComposable(myList: List<String>) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
            }
        }
        Text("Count: ${myList.size}")
    }
}

Ten kod nie powoduje efektów ubocznych i przekształca listę wejściową w UI. To świetnie do wyświetlania niewielkiej listy. Jeśli jednak funkcja zapisuje , ten kod nie będzie bezpiecznym wątkiem ani nie będzie poprawny:

@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}

W tym przykładzie element items jest modyfikowany przy każdej zmianie kompozycji. Może to być w każdej klatce animacji lub gdy lista jest aktualizowana. W obu przypadkach wyświetli niewłaściwą liczbę. Z tego powodu tego typu zapisy nie są obsługiwane w Utwórz; zakazując takiego zapisu, umożliwiamy platformie zmienianie wątków aby wykonywać elementy lambda z możliwością kompozycyjnej.

Dostosowywanie jest w miarę możliwości pomijane

Jeśli fragmenty interfejsu użytkownika są nieprawidłowe, funkcja Utwórz, by ponownie utworzyć które wymagają aktualizacji. Oznacza to, że może on zostać pominięty, by ponownie uruchomić Funkcja kompozycyjna przycisku bez wykonywania któregokolwiek z elementów kompozycyjnych znajdujących się powyżej lub poniżej niego w drzewie UI.

Każda funkcja kompozycyjna i lambda mogą się ponownie skomponować. Oto przykład ilustrujący, jak przez zmianę kompozycji można pominąć niektóre elementy podczas renderowania listy:

/**
 * Display a list of names the user can click with a header
 */
@Composable
fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) -> Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.bodyLarge)
        Divider()

        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name ->
                // When an item's [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}

/**
 * Display a single name the user can click.
 */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}

Każdy z tych zakresów może być jedyną rzeczą, którą należy wykonać podczas ponownej kompozycji. Narzędzie Compose może przejść do funkcji lambda Column bez wykonywania któregoś z elementów nadrzędnych gdy zmieni się header. Podczas wykonywania zadania Column funkcja tworzenia wiadomości może pomiń elementy elementu LazyColumn, jeśli names nie zostały zmienione.

Również w tym przypadku wykonywanie wszystkich funkcji kompozycyjnych i lambda powinno odbywać się bez efektów ubocznych. Jeśli chcesz wykonać efekt uboczny, wywołaj go za pomocą wywołania zwrotnego.

Zmiana kompozycji jest optymistyczna

Ponowna kompozycja rozpoczyna się zawsze, gdy funkcja tworzenia uzna, że parametry elementu kompozycyjnego mógł się zmienić. Zmiana kompozycji jest optymistyczna, co oznacza, że funkcja tworzenia wiadomości oczekuje aby dokończyć przekomponowanie, zanim parametry ulegną ponownej zmianie. Jeśli parametr zmienia się przed zakończeniem zmiany kompozycji, funkcja tworzenia może anulować i uruchomić ją ponownie z nowym parametrem.

Po anulowaniu ponownej kompozycji funkcja Utwórz odrzuca drzewo UI z zmianę kompozycji. Jeśli występują skutki uboczne zależne od tego, efekt uboczny zostanie zastosowany, nawet jeśli kompozycja zostanie anulowana. Może to prowadzić do niespójnego stanu aplikacji.

Sprawdź, czy wszystkie funkcje kompozycyjne i lambda są idempotentne i skuteczne i zająć się optymistyczną zmianą kompozycji.

Funkcje kompozycyjne mogą być uruchamiane dość często

W niektórych przypadkach funkcja kompozycyjna może być uruchamiana dla każdej klatki w interfejsie animację. Jeśli funkcja wykonuje drogie operacje, takie jak odczyt pamięci urządzenia, funkcja może powodować zacinanie się interfejsu.

Jeśli na przykład widżet próbuje odczytać ustawienia urządzenia, może potencjalnie odczytują te ustawienia setki razy na sekundę, co negatywnie wpływa na wydajność aplikacji.

Jeśli funkcja kompozycyjna wymaga danych, powinna zdefiniować parametry danych. Możesz przenieść kosztowne zadania do innego wątku, poza i przekazać dane do Compose za pomocą funkcji mutableStateOf lub LiveData.

Więcej informacji

Aby dowiedzieć się więcej o tym, jak myśleć w funkcjach tworzenia i kompozycji, zapoznaj się z poniższe dodatkowe materiały.

Filmy

. .