Архитектура пользовательского интерфейса Compose

В Compose пользовательский интерфейс является неизменяемым — его невозможно обновить после того, как он был нарисован. Что вы можете контролировать, так это состояние вашего пользовательского интерфейса. Каждый раз, когда состояние пользовательского интерфейса меняется, Compose воссоздает изменившиеся части дерева пользовательского интерфейса . Составные элементы могут принимать состояние и предоставлять события — например, TextField принимает значение и предоставляет обратный вызов onValueChange , который запрашивает обработчик обратного вызова для изменения значения.

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

Поскольку составные объекты принимают состояние и предоставляют события, шаблон однонаправленного потока данных хорошо подходит для Jetpack Compose. В этом руководстве основное внимание уделяется тому, как реализовать шаблон однонаправленного потока данных в Compose, как реализовать события и держатели состояний, а также как работать с моделями представления в Compose.

Однонаправленный поток данных

Однонаправленный поток данных (UDF) — это шаблон проектирования, в котором состояние течет вниз, а события — вверх. Следуя однонаправленному потоку данных, вы можете отделить составные элементы, отображающие состояние в пользовательском интерфейсе, от частей вашего приложения, которые сохраняют и изменяют состояние.

Цикл обновления пользовательского интерфейса для приложения, использующего однонаправленный поток данных, выглядит следующим образом:

  • Событие : часть пользовательского интерфейса генерирует событие и передает его вверх, например, нажатие кнопки передается в ViewModel для обработки; или событие передается из других слоев вашего приложения, например, указывая, что срок сеанса пользователя истек.
  • Состояние обновления : обработчик событий может изменить состояние.
  • Состояние отображения : держатель состояния передает состояние, и пользовательский интерфейс отображает его.

Рисунок 1. Однонаправленный поток данных.

Следование этому шаблону при использовании Jetpack Compose дает несколько преимуществ:

  • Тестируемость : отделение состояния от пользовательского интерфейса, который его отображает, упрощает тестирование обоих по отдельности.
  • Инкапсуляция состояния . Поскольку состояние может быть обновлено только в одном месте и существует только один источник истины для состояния составного объекта, вероятность возникновения ошибок из-за несогласованности состояний снижается.
  • Согласованность пользовательского интерфейса . Все обновления состояния немедленно отражаются в пользовательском интерфейсе с помощью наблюдаемых держателей состояний, таких как StateFlow или LiveData .

Однонаправленный поток данных в Jetpack Compose

Составные элементы работают на основе состояния и событий. Например, TextField обновляется только тогда, когда обновляется его параметр value , и он предоставляет обратный вызов onValueChange — событие, которое запрашивает изменение значения на новое. Compose определяет объект State как держатель значения, а изменения значения состояния вызывают рекомпозицию. Вы можете хранить состояние в remember { mutableStateOf(value) } или rememberSaveable { mutableStateOf(value) в зависимости от того, как долго вам нужно запомнить значение.

Типом значения составного объекта TextField является String , поэтому оно может быть получено откуда угодно — из жестко запрограммированного значения, из ViewModel или передано из родительского составного объекта. Вам не обязательно хранить его в объекте State , но вам нужно обновить значение при вызове onValueChange .

Определите составные параметры

При определении параметров состояния составного объекта следует учитывать следующие вопросы:

  • Насколько многоразовым и гибким является компонуемое?
  • Как параметры состояния влияют на производительность этого компонуемого объекта?

Чтобы стимулировать развязку и повторное использование, каждый компонуемый объект должен содержать как можно меньше информации. Например, при создании составного объекта для хранения заголовка новостной статьи предпочитайте передавать только ту информацию, которая должна отображаться, а не всю новостную статью:

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

Иногда использование отдельных параметров также повышает производительность — например, если News содержит больше информации, чем просто title и subtitle , всякий раз, когда новый экземпляр News передается в Header(news) , составной элемент будет перекомпонован, даже если title и subtitle не были изменены. измененный.

Тщательно продумайте количество передаваемых параметров. Наличие функции со слишком большим количеством параметров снижает эргономику функции, поэтому в этом случае предпочтительнее группировать их в класс.

События в Compose

Каждый ввод в ваше приложение должен быть представлен как событие: касания, изменения текста и даже таймеры или другие обновления. Поскольку эти события меняют состояние вашего пользовательского интерфейса, ViewModel должна обрабатывать их и обновлять состояние пользовательского интерфейса.

Уровень пользовательского интерфейса никогда не должен изменять состояние за пределами обработчика событий, поскольку это может привести к несогласованности и ошибкам в вашем приложении.

Предпочитайте передачу неизменяемых значений для лямбда-выражений обработчиков состояний и событий. Этот подход имеет следующие преимущества:

  • Вы улучшаете возможность повторного использования.
  • Вы гарантируете, что ваш пользовательский интерфейс не меняет значение состояния напрямую.
  • Вы избегаете проблем с параллелизмом, поскольку гарантируете, что состояние не изменяется из другого потока.
  • Часто вы уменьшаете сложность кода.

Например, составной объект, который принимает String и лямбду в качестве параметров, может вызываться из многих контекстов и допускает многократное использование. Предположим, что верхняя панель вашего приложения всегда отображает текст и имеет кнопку «Назад». Вы можете определить более общий составной объект MyAppTopAppBar , который получает текст и дескриптор кнопки «Назад» в качестве параметров:

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.AutoMirrored.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModels, состояния и события: пример

Используя ViewModel и mutableStateOf , вы также можете ввести однонаправленный поток данных в своем приложении, если выполняется одно из следующих условий:

  • Состояние вашего пользовательского интерфейса отображается через наблюдаемые держатели состояний, такие как StateFlow или LiveData .
  • ViewModel обрабатывает события, поступающие из пользовательского интерфейса или других слоев вашего приложения, и обновляет держатель состояния на основе этих событий.

Например, при реализации экрана входа в систему нажатие кнопки «Войти» должно привести к тому, что ваше приложение отобразит индикатор выполнения и сетевой вызов. Если вход в систему прошел успешно, ваше приложение перейдет на другой экран; в случае ошибки приложение отображает Snackbar. Вот как можно смоделировать состояние экрана и событие:

Экран имеет четыре состояния:

  • Вышел : когда пользователь еще не вошел в систему.
  • В процессе : когда ваше приложение в данный момент пытается войти в систему, выполняя сетевой вызов.
  • Ошибка : когда произошла ошибка при входе в систему.
  • Вошёл : когда пользователь вошел в систему.

Вы можете моделировать эти состояния как запечатанный класс. ViewModel предоставляет состояние как State , устанавливает начальное состояние и обновляет его по мере необходимости. ViewModel также обрабатывает событие входа в систему, предоставляя метод onSignIn() .

class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<UiState>(UiState.SignedOut)
    val uiState: State<UiState>
        get() = _uiState

    // ...
}

В дополнение к API mutableStateOf Compose предоставляет расширения для LiveData , Flow и Observable для регистрации в качестве прослушивателя и представления значения в виде состояния.

class MyViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UiState>(UiState.SignedOut)
    val uiState: LiveData<UiState>
        get() = _uiState

    // ...
}

@Composable
fun MyComposable(viewModel: MyViewModel) {
    val uiState = viewModel.uiState.observeAsState()
    // ...
}

Узнать больше

Чтобы узнать больше об архитектуре Jetpack Compose, обратитесь к следующим ресурсам:

Образцы

Mir 2: Return of the King 是 Actoz Soft 授权的优质《传奇》IP 移动游戏,由 HK ZHILI YAOAN LIMITED 使用 Unity 游戏引擎开发。 这款游戏不仅完美再现了韩国奇幻类 MMORPG 的代表作 Mir 2 的游戏氛围,还提供了许多最受欢迎的游戏内容,例如装备收集、大规模沙漠攻击和其他核心玩法。 该游戏使用了 Android Frame Pacing 库 (Swappy) 来提高帧速率的稳定性、实现流畅的渲染,并显著提升了 Android

鸣潮 是一款由 Kuro Games 开发的高保真动作角色扮演游戏。为了持续为长时间的游戏会话提供卓越的用户体验,优化功耗非常重要。 Android Studio 从 Hedgehog (2023.1.1) 开始引入了 功耗性能分析器 ,可帮助开发者根据设备端电源轨监视器 (ODPM) 了解功耗数据。 借助 Android Studio 中的功耗性能分析功能,您还可以 有效地对 Android 应用功能的功耗进行 A/B 测试 (如下所示)。 Kuro Games 首先使用 Android

Godot Engine 是一个广受欢迎的多平台开源游戏引擎,对 Android 提供强大的支持。Godot 可用于制作几乎任何类型的游戏,并且支持 2D 和 3D 图形。Godot 4 版引入了新的渲染系统,该系统具有用于高保真图形的高级功能。Godot 4 渲染程序专为 Vulkan 等现代图形 API 而设计。 Godot Foundation 聘请了 The Forge Interactive 的图形优化专家,并与 Google 合作分析和进一步改进了 Godot 4 Vulkan

{% дословно %}

Пока рекомендаций нет.

Попытайтесь в свой аккаунт Google.

{% дословно %}