Основы Kotlin Multiplatform. Бесплатный учебник на русском языке (KA0700)
Основы Kotlin Multiplatform. Бесплатный учебник на русском языке (KA0700)

KA0705 — Разработка пользовательского интерфейса: Compose Multiplatform (CM)

  • Запись изменена:20.12.2022
  • Post category:Публикации
  • Reading time:27 минут чтения

Если вы работаете с мобильными устройствами, приятно узнать, что вы можете создавать приложения для настольных компьютеров, используя знания, полученные при изучении Jetpack Compose (JC). JetBrains, разработчик технологии, лежащей в основе Android Studio и IntelliJ, работал с Google над созданием Compose Multiplatform (CM). Это использует часть того же кода из Jetpack Compose, но расширяет его для использования на нескольких платформах. В этой главе основное внимание будет уделено рабочему столу, но CM будет работать и в Интернете.

Знакомство с Составлением Мультиплатформенной

CM использует виртуальную машину Java (JVM) под капотом, так что вы все равно можете использовать старую технологию Swing, если хотите. Он использует графическую библиотеку Skia, которая обеспечивает аппаратное ускорение (например, JC). Приложения, созданные с помощью CM, могут работать на macOS, Windows и Linux.

Различия в рабочем столе

В отличие от мобильных устройств, на рабочем столе есть такие функции, как меню, несколько окон и системные уведомления. Меню могут иметь ярлыки, а окна будут иметь разные размеры и положения на экране. На рабочем столе обычно не используются панели приложений, как в мобильных приложениях. Обычно вы используете меню для обработки действий.

Создание настольного приложения

Чтобы создать настольное приложение, вам нужно сделать несколько вещей:

  1. Создайте модуль рабочего стола.
  2. Создайте общий модуль пользовательского интерфейса.
  3. Переместите большую часть кода Android в общий модуль пользовательского интерфейса.
  4. Создайте несколько оболочек, чтобы Android и desktop могли иметь уникальную функциональность.

Обновление файлов Gradle

Для начала вам нужно будет обновить несколько ваших текущих файлов Gradle. Откройте начальный проект в Android Studio и откройте основной build.gradle.kts. В разделе все проекты и в конце репозиториев добавьте:

maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")

Это добавляет репозиторий для мультиплатформенной библиотеки Compose. Откройте общий файл build.gradle.kts и добавьте следующее после kotlin -> android():

jvm("desktop"){
    compilations.all {
        kotlinOptions.jvmTarget = "11"
    }
}

Приведенный выше код создает новую цель JVM с именем desktop и устанавливает версию JDK равной 11.

Настольный модуль

Нет простого способа создать настольный модуль, кроме как вручную. На момент написания статьи JetBrains работает над улучшением этого, но это не так сложно сделать вручную. Щелкните правой кнопкой мыши папку верхнего уровня в окне проекта и выберите Создать ▸ Каталог:

Рис. 5.1 - Создание нового каталога
Рис. 5.1 — Создание нового каталога

Назовите каталог desktop. Затем щелкните правой кнопкой мыши на папке рабочего стола и выберите Создать ▸ Файл. Назовите файл build.gradle.kts. Этот файл сборки аналогичен файлу сборки общего модуля. Добавьте следующее:

import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

// 1
plugins {
    kotlin(multiplatform)
    id(composePlugin) version Versions.desktop_compose_plugin
}

// 2
group = "com.raywenderlich.findtime"
version = "1.0.0"

// 3
kotlin {
  // TODO: Add Kotlin 
}

// TODO: Add Compose Desktop
  1. Добавьте мультиплатформенные и настольные плагины Compose.
  2. Установите группу и версию.
  3. Настройте параметры рабочего стола Kotlin.

// TODO: Add KotlinЗаменить следующим кодом:

// 1
jvm {
    withJava()
    compilations.all {
        kotlinOptions.jvmTarget = "11"
    }
}
// 2
sourceSets {
    val jvmMain by getting {
        // 3
        kotlin.srcDirs("src/jvmMain/kotlin")
        dependencies {
            // 4
            implementation(compose.desktop.currentOs)
            // 5
            api(compose.runtime)
            api(compose.foundation)
            api(compose.material)
            api(compose.ui)
            api(compose.materialIconsExtended)

            implementation(Deps.napier)
            // Coroutines
            implementation(Deps.Coroutines.common)

            // 6
            implementation(project(":shared"))
//            implementation(project(":shared-ui"))
        }
    }
}
  1. Настройте цель JVM, которая использует Java 11 (требуется 11 или выше).
  2. Настройте группу источников и ресурсов для JVM.
  3. Задайте путь к исходному каталогу.
  4. Используйте предопределенную переменную, чтобы ввести текущую библиотеку ОС для создания.
  5. Принесите библиотеки compose. (Заранее определенные переменные).
  6. Импортируйте ваши общие библиотеки. Оставьте общий пользовательский интерфейс закомментированным, пока вы его не создадите.

Здесь много всего, но настройка desktop Gradle немного сложнее. Это настраивает библиотеки, необходимые для модуля desktop.

Заменить // TODO: Add Compose Desktopна:

// 1
compose.desktop {
    // 2
    application {
        // 3
        mainClass = "MainKt"
        // 4
        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "FindTime"
            macOS {
            	bundleID = "com.raywenderlich.findtime"
        		}
        }
    }
}
  1. Конфигурация для создания рабочего стола.
  2. Определите приложение.
  3. Установите основной класс. Через некоторое время вы создадите файл Main.kt.
  4. Настройте информацию об упаковке на момент, когда вы будете готовы к отправке.

Нажмите Синхронизировать сейчас в верхней правой части окна. Откройте settings.gradle.kts из корневого каталога и добавьте новый проект в конец файла:

include(":desktop")

Выполните еще одну синхронизацию.

Далее щелкните правой кнопкой мыши на папке рабочего стола и выберите Создать ▸ Каталог. Используйте src/jvmMain/kotlin. Это создаст три папки: srcjvmMain и kotlin под этим. Далее щелкните правой кнопкой мыши на папке kotlin и выберите Создать ▸ Класс/файл Kotlin. Введите Main и выберите file.

Замените содержимое файла следующим кодом:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState

// 1
fun main() {
    // 2
    application {
        // 3
        val windowState = rememberWindowState()

        // 4
        Window(
            onCloseRequest = ::exitApplication,
            state = windowState,
            title = "TimeZone"
        ) {
            // 5
            Surface(modifier = Modifier.fillMaxSize()) {
              // TODO: Add Theme
              // TODO: Add MainView
            }
        }
    }
}
  1. Точка входа в приложение. Так же, как в программах Kotlin или Java, начальной функцией является main .
  2. Создайте новое приложение.
  3. Запомните текущее состояние окна по умолчанию. Измените это, если вы хотите, чтобы окно было расположено в другом положении или другого размера.
  4. Создайте новое окно с состоянием окна. Если пользователь закроет окно, выйдите из приложения.
  5. Настройте Surface.

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

Общий пользовательский интерфейс

Ранее вы создали файлы Android Compose. Вы вложили много труда в эти файлы. Вы могли бы скопировать эти файлы для рабочего стола, но почему бы не поделиться ими? Это идея, лежащая в основе модуля общего пользовательского интерфейса. Вы переместите файлы Android и внесете несколько изменений, чтобы их можно было использовать как для Android, так и для рабочего стола.

В окне проекта щелкните правой кнопкой мыши на папке верхнего уровня и выберите Создать ▸ Каталог. Назовите каталог shared-ui. Затем щелкните правой кнопкой мыши на папке общего пользовательского интерфейса и выберите Создать ▸ Файл. Назовите файл build.gradle.kts. Добавьте следующее:

import org.jetbrains.compose.compose

plugins {
    kotlin(multiplatform)
    id(androidLib)
    id(composePlugin) version Versions.desktop_compose_plugin
}

android {
   // TODO: Add Android Info
}

kotlin {
     // TODO: Add Desktop Info
}

Это добавляет мультиплатформенные, Android и Compose плагины. Теперь замените // TODO: Add Android Infoна:

compileSdk =  Versions.compile_sdk
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
    minSdk = Versions.min_sdk
    targetSdk = Versions.target_sdk
}
buildTypes {
    getByName("release") {
        isMinifyEnabled = false
    }
}

Это минимальная информация, необходимая для Android. Для рабочего стола замените // TODO: Add Desktop Infoна:

// 1
android()
// 2
jvm("desktop") {
    compilations.all {
        kotlinOptions.jvmTarget = "11"
    }
}

sourceSets {
    // 3
    val commonMain by getting {
        dependencies {
            implementation(project(":shared"))

            api(compose.foundation)
            api(compose.runtime)
            api(compose.foundation)
            api(compose.material)
            api(compose.materialIconsExtended)
            api(compose.ui)
            api(compose.uiTooling)
        }
    }
    val commonTest by getting
    val androidMain by getting {
        dependencies {
            implementation("androidx.appcompat:appcompat:1.3.1")
        }
    }
    val desktopMain by getting
}
  1. Установите цель для Android.
  2. Установите целевой параметр рабочего стола.
  3. Определите общие основные источники. Это включает в себя общую библиотеку и Desktop Compose.

Одной из приятных особенностей CM является то, что его можно использовать как с Android, так и с настольными компьютерами и веб-сайтами. Для папки общего пользовательского интерфейса вам понадобятся три разных исходных каталога. Один для Android, один для общего источника и один для рабочего стола. Щелкните правой кнопкой мыши на shared-ui и выберите New ▸ Каталог.

Введите src/androidMain/kotlin/com/raywenderlich/compose/ui.

Это создаст несколько папок. Затем сделайте то же самое для commonMain. Выберите каталог src, который вы только что создали, и создайте новый каталог с именем commonMain/kotlin/com/raywenderlich/compose .

Для рабочего стола сделайте то же самое, но используйте: desktopMain/kotlin/com/raywenderlich/compose/ui.

Это создаст три основных каталога. Первый для Android, второй для всего общего кода и третий для рабочего стола.

Откройте settings.gradle.kts из корневого каталога и добавьте новый проект:

include(":shared-ui")

Нажмите Синхронизировать сейчас. Теперь начинается самое интересное. Вместо того чтобы воссоздавать весь пользовательский интерфейс Compose для рабочего стола, вы украдете его с Android. В AndroidApp /src /main/java /com/raywenderlich /findtime /android выберите папки темы и пользовательского интерфейса. Перетащите две папки в папку shared-ui/src/commonMain/kotlin/com/raywenderlich/compose. Вы получите несколько предупреждений, но продолжайте. Вы исправите эти проблемы сейчас.

AddTimeZoneDialog

Откройте файл AddTimeZoneDialog.kt из папки commonMain/kotlin/com/raywenderlich/compose/ui внутри модуля общего пользовательского интерфейса. Вы увидите несколько ошибок при следующем импорте:

import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.Dialog
import com.raywenderlich.findtime.android.R

Эти три импорта не существуют для модуля общего пользовательского интерфейса. Удалите их. После импорта добавьте:

@Composable
expect fun AddTimeDialogWrapper(onDismiss: onDismissType, content: @Composable () -> Unit)

Это составная функция, которая использует expectключевое слово KMM. Это означает, что каждая цель, которую использует этот модуль, должна реализовать эту функцию. Теперь измените сигнатуру для AddTimeZoneDialogфункции и кода до первой Columnс помощью следующего кода:

fun AddTimeZoneDialog(
    onAdd: OnAddType,
    onDismiss: onDismissType
) {
    val timezoneHelper: TimeZoneHelper = TimeZoneHelperImpl()

    AddTimeDialogWrapper(onDismiss) {

Это просто использует AddTimeDialogWrapperфункцию для переноса существующего кода. AddTimeDialogWrapperФункция будет обрабатывать код, зависящий от платформы. Android будет обрабатывать диалоговое окно одним способом, а рабочий стол — другим. Перейдите к концу этой функции и добавьте окончание }.

Одной из проблем при использовании Desktop Compose является обработка ресурсов. Это выходит за рамки данной главы. На данный момент просто измените строковые ресурсы на жестко запрограммированные строки. Изменение:

stringResource(id = R.string.cancel)

Для:

"Cancel"

Изменение:

stringResource(id = R.string.add)

Для:

"Add"

В папке shared-ui/src/androidMain/kotlin/com/raywenderlich/compose/ui щелкните правой кнопкой мыши и создайте новый файл Kotlin с именем AddTimeDialogWrapper.kt. Это будет версия Android, которая реализует expectопределенное в commonMain. Добавить:

import androidx.compose.runtime.Composable
import androidx.compose.ui.window.Dialog
import com.raywenderlich.compose.ui.onDismissType

@Composable
actual fun AddTimeDialogWrapper(onDismiss: onDismissType, content: @Composable () -> Unit) {
    Dialog(
        onDismissRequest = onDismiss) {
        content()
    }
}

Это создает функцию, которая принимает обратный вызов dismiss и содержимое для диалогового окна. Причина, по которой вам это нужно, заключается в том, что Dialogона специфична для JC, а не для CM. Убедитесь, что при добавлении файла он является частью com.raywenderlich.compose.uiпакета, если это еще не так. Щелкните правой кнопкой мыши на desktopMain/kotlin/com/raywenderlich/compose/ui и создайте тот же класс AddTimeDialogWrapper.kt. Добавить:

import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogState
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberDialogState

@Composable
actual fun AddTimeDialogWrapper(onDismiss: onDismissType, content: @Composable () -> Unit) {
    Dialog(onCloseRequest = { onDismiss() },
        state = rememberDialogState(
            position = WindowPosition(Alignment.Center),
        ),
            title = "Add Timezones",
            content = {
                content()
            })
}

Этот класс похож, и вы можете спросить, зачем вам вообще нужно создавать эту оболочку. Они оба относятся к import androidx.compose.ui.window.Dialog. Но если вы нажмете на каждый из этих импортных файлов, вы увидите, что они переходят к двум разным файлам. Плагин CM выполняет некоторую замену пакетами, которая позволяет двум библиотекам работать вместе, но часть кода должна отличаться. Dialogsявляются одним из таких случаев. Здесь для этого Dialogтребуется отклонить обратный вызов, состояние, заголовок и содержимое. К счастью, это не так уж много кода. Основная часть кода создания находится в AddTimeZoneDialog.

Диалог собраний

Очень похоже AddTimeZoneDialogна то , что тебе нужно измениться MeetingDialog. Откройте MeetingDialog.kt и удалите импорт, который отображается красным цветом. Добавьте еще одну обертку:

@Composable
expect fun MeetingDialogWrapper(onDismiss: onDismissType, content: @Composable () -> Unit)

Это точно так же, как и другая оболочка диалогового окна. Теперь измените MeetingDialogметод на Columnследующий:

fun MeetingDialog(
    hours: List<Int>,
    onDismiss: onDismissType
) {

    MeetingDialogWrapper(onDismiss) {

Это добавляет оболочку вокруг диалогового окна. Обязательно добавьте закрытие}, как и раньше.

Изменение:

stringResource(id = R.string.done)

Для:

"Done"

В папке src/androidMain/kotlin/com/raywenderlich/compose/ui щелкните правой кнопкой мыши и создайте новый файл Kotlin с именем MeetingDialogWrapper.kt. Это будет версия Android, которая реализует expectопределенное в commonMain.

Добавить:

import androidx.compose.runtime.Composable
import androidx.compose.ui.window.Dialog

@Composable
actual fun MeetingDialogWrapper(onDismiss: onDismissType, content: @Composable () -> Unit) {
    Dialog(
        onDismissRequest = onDismiss) {
        content()
    }
}

Это создает функцию, которая принимает обратный вызов dismiss и содержимое для диалогового окна. Щелкните правой кнопкой мыши на рабочем столе/kotlin/com/raywenderlich/compose/ui и создайте тот же класс MeetingDialogWrapper.kt. Добавить:

import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.rememberDialogState

@Composable
actual fun MeetingDialogWrapper(onDismiss: onDismissType, content: @Composable () -> Unit) {
    Dialog(
        onCloseRequest = { onDismiss() },
        title = "Meetings",
        state = rememberDialogState(),
        content = {
            content()
        })
}

Это добавляет обработчик закрытия, заголовок “Собрания”, состояние диалога и содержимое.

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

Рис. 5.2 - Редактирование конфигураций
Рис. 5.2 — Редактирование конфигураций

Затем щелкните значок плюса и выберите Gradle.

Рис. 5.3 - Добавление новой конфигурации
Рис. 5.3 — Добавление новой конфигурации

Затем выполните следующие действия:

  1. Установите имя на Рабочий стол.
  2. Для проекта Gradle выберите проект рабочего стола.
  3. В поле Задачи введите выполнить.

Нажмите кнопку ОК.

Рис. 5.4 - Конфигурация рабочего стола
Рис. 5.4 — Конфигурация рабочего стола

Запустите настольное приложение:

Рис. 5.5 - Запуск конфигурации рабочего стола
Рис. 5.5 — Запуск конфигурации рабочего стола

Подождите, что это такое?

Рис. 5.6 - Пустое приложение для рабочего стола
Рис. 5.6 — Пустое приложение для рабочего стола

Хорошая новость заключается в том, что приложение запустилось. Плохая новость в том, что там нет никакого контента. Ты знаешь почему? Правильно — вы никогда не добавляли никакого контента в Main.kt. Вернитесь к Main.kt в модуле desktop. Внутри поверхности добавьте:

AppTheme {
    MainView()
}

Это показывает ошибки. Есть идеи? Взгляните на файл desktop build.gradle.kts. Похоже, вам нужно раскомментировать проект с общим пользовательским интерфейсом. Вам придется прекратить запуск рабочего стола, чтобы выполнять любые другие задачи Gradle. Нажмите красную кнопку остановки, раскомментируйте проект с общим пользовательским интерфейсом и повторно синхронизируйте Gradle.

Теперь добавьте недостающий импорт в Main.kt и снова запустите приложение.

Рис. 5.7 - Экран часовых поясов рабочего стола
Рис. 5.7 — Экран часовых поясов рабочего стола

Намного лучше. Попробуйте воспользоваться приложением и посмотрите, не упустили ли вы чего-нибудь.

Рис. 5.8 - Экран рабочего стола с временным диапазоном
Рис. 5.8 — Экран рабочего стола с временным диапазоном

Примечание: Фон окна может меняться в зависимости от того, используете ли вы Темную тему на своем компьютере или нет.

Рис. 5.9 - Экран времени совещаний на рабочем столе
Рис. 5.9 — Экран времени совещаний на рабочем столе

Размеры окон

Если вы откроете диалоговое окно Добавления часовых поясов, вы увидите, что кнопки будут отключены:

Рис. 5.10 - Обрезанные кнопки окна рабочего стола
Рис. 5.10 — Обрезанные кнопки окна рабочего стола

Как вы можете это исправить? В диалоговых окнах есть DialogStateкласс, который позволяет вам устанавливать положение и размер. Чтобы исправить это диалоговое окно, откройте AddTimeDialogWrapper внутри desktopMain и добавьте в rememberDialogStateметод, чтобы он выглядел следующим образом:

state = rememberDialogState(
    position = WindowPosition(Alignment.Center),
    size = DpSize(width = 400.dp, height = Dp.Unspecified),
),

Это устанавливает фиксированную ширину 400dp и неуказанную высоту. Это позволит увеличить высоту до хорошего размера.

Для MeetingDialogWrapper замените rememberDialogStateметод на:

rememberDialogState(size = DpSize(width = 400.dp, height = Dp.Unspecified))

Создайте и запустите настольное приложение. Вы увидите, что кнопки больше не обрезаны:

Рис. 5.11 - Кнопки окна рабочего стола не обрезаны
Рис. 5.11 — Кнопки окна рабочего стола не обрезаны

Windows

Ваше приложение может иметь одно окно или несколько окон. Если у вас есть только одно окно, вы можете использовать singleWindowApplicationвместо application. Для нескольких окон вам необходимо вызвать Windowфункцию для каждого окна.

Откройте Main.kt в модуле desktop. Прежде fun main()чем добавить:

data class WindowInfo(val windowName: String, val windowState: WindowState)

@OptIn(ExperimentalComposeUiApi::class)

Добавьте любой необходимый импорт. WindowInfoКласс просто содержит имя окна и состояние окна.

Удалите val windowState = rememberWindowState(), затем добавьте:

var initialized by remember { mutableStateOf(false) }
var windowCount by remember { mutableStateOf(1) }
val windowList = remember { SnapshotStateList<WindowInfo>() }
// Add initial window
if (!initialized) {
    windowList.add(WindowInfo("Timezone-${windowCount}", rememberWindowState()))
    initialized = true
}

Добавьте любой необходимый импорт , например import androidx.compose.runtime.*. Приведенный выше код создает три переменные:

  1. Одноразовый initializedфлаг.
  2. Количество открытых окон (начиная с одного).
  3. Список окон.

Затем он добавляет первую запись окна (только один раз). Это будет первое окно, которое появится.

Замените Windowфункцию на:

// 1
windowList.forEachIndexed { i, window ->
    Window(
        onCloseRequest = {
            // 2
            windowList.removeAt(i)
        },
        state = windowList[i].windowState,
        // 3
        title = windowList[i].windowName
    )
  1. Для каждого WindoInfoкласса в вашем списке создайте новое окно.
  2. Когда окно будет закрыто, удалите его из списка.
  3. Установите заголовок на имя из WindowInfoкласса.

Затем добавьте окончание }в конце application. С помощью приведенного выше кода теперь у вас может быть несколько окон вашего настольного приложения. Вы увидите это в действии в следующем разделе.

Меню

Если вы посмотрите на строку меню в macOS, вы заметите, что в вашем приложении нет меню, как в обычном приложении:

Рис. 5.12 - Меню рабочего стола macOS
Рис. 5.12 — Меню рабочего стола macOS

Теперь вы добавите несколько пунктов меню, таких как меню «Файл» и «Редактирование», а также пункт меню «Выход» под меню «Файл», чтобы пользователь мог выйти из приложения.

Перед функцией Surface добавьте код для a MenuBarследующим образом:

// 1
MenuBar {
    // 2
    Menu("File", mnemonic = 'F') {
        val nextWindowState = rememberWindowState()
        // 3
        Item(
            "New", onClick = {
                // 4
                windowCount++
                windowList.add(
                    WindowInfo(
                        "Timezone-${windowCount}",
                        nextWindowState
                    )
                )
            }, shortcut = KeyShortcut(
                Key.N, ctrl = true
            )
        )
        Item("Open", onClick = { }, shortcut = KeyShortcut(Key.O, ctrl = true))
        // 5
        Item("Close", onClick = {
            windowList.removeAt(i)

        }, shortcut = KeyShortcut(Key.W, ctrl = true))
        Item("Save", onClick = { }, shortcut = KeyShortcut(Key.S, ctrl = true))
        // 6
        Separator()
        // 7
        Item(
            "Exit",
            onClick = { windowList.clear() },
        )
    }
    Menu("Edit", mnemonic = 'E') {
      Item(
        "Cut", onClick = { }, shortcut = KeyShortcut(
          Key.X, ctrl = true
        )
      )
      Item(
        "Copy", onClick = { }, shortcut = KeyShortcut(
          Key.C, ctrl = true
        )
      )
      Item("Paste", onClick = { }, shortcut = KeyShortcut(Key.V, ctrl = true))
    }
}
  1. Создайте панель меню для хранения всех ваших меню.
  2. Создайте новое меню с именем File.
  3. Создайте пункт меню с именем New.
  4. Увеличьте количество окон и добавьте новый WindowInfoкласс в список. Это приведет к повторному выполнению функции.
  5. Закройте текущее окно, удалив его из списка.
  6. Добавьте разделитель.
  7. Добавьте меню выхода. Это очистит список окон, что приведет к закрытию приложения.

Добавьте любой необходимый импорт. Большинство из этих меню ничего не делают. Пункт меню «Файл» увеличит количество окон, меню «Закрыть» удалит окно из списка окон, а меню «Выход» очистит список (что приведет к завершению работы приложения). Запустите приложение. Вот что вы увидите:

Рис. 5.13 - Рабочий стол с несколькими окнами
Рис. 5.13 — Рабочий стол с несколькими окнами

Попробуйте создать новые окна и закрыть их. Посмотрите, что произойдет, когда вы закроете последнее окно.

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

Распространение

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

Примечание: На момент написания статьи для сборки дистрибутива macOS требовалась версия Java 15 или выше. На M1 MacBook pro вы обнаружите, что дистрибутив JVM на базе Azul Arm работает хорошо и прост в установке. Существует множество пакетов сторонних производителей. Выполните поиск в Google, чтобы найти тот, который легко установить на ваш компьютер.

Чтобы создать установщик dmg для Mac, вам необходимо запустить задачу package Gradle . Вы можете запустить его из Android Studio:

Рис. 5.14 - Задача пакета Gradle desktop
Рис. 5.14 — Задача пакета Gradle desktop

Или выполните следующее из командной строки (в каталоге проекта):

./gradlew :desktop:package

Это создаст пакет в папке ./desktop/build/compose/binaries/main/dmg . Откройте его, и вы увидите свое приложение. Вы можете дважды щелкнуть приложение, чтобы запустить его, или перетащить его в папку приложений.

Вот как будет выглядеть ваше окончательное приложение:

Рис. 5.15 - Настольное приложение, запущенное на macOS из упакованного dmg
Рис. 5.15 — Настольное приложение, запущенное на macOS из упакованного dmg

Windows

В Windows процесс почти такой же. Убедитесь, что вы создали пакет desktop из Android Studio, а затем из командной строки введите:

.\gradlew.bat :desktop:package

Или запустите задачу package из папки compose desktop task. Как только это закончится, вы найдете файл FindTime-1.0.0.msi в папке desktop/build/compose/binaries/main/msi.

Рис. 5.16 - Настольное приложение, запущенное в Windows из упакованного дистрибутива
Рис. 5.16 — Настольное приложение, запущенное в Windows из упакованного дистрибутива

Обновление приложения для Android

В то время как перемещение всего кода пользовательского интерфейса было отличным для рабочего стола, это сломало приложение для Android. Но ты можешь это исправить. Во-первых, вам необходимо обновить файл build.gradle.kts в модуле androidMain. Добавьте библиотеку общего пользовательского интерфейса после общей библиотеки:

implementation(project(":shared-ui"))

Запустите синхронизацию Gradle. В проекте общего пользовательского интерфейса добавьте новый AndroidManifest.xml файл в папке src/androidMain. Затем добавьте:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.raywenderlich.findtime.shared_ui" />

Запустите приложение для Android, чтобы убедиться, что оно все еще работает.

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

Ключевые моменты

  • Compose Multiplatform — это платформа, которая использует большую часть Jetpack Compose framework для отображения пользовательских интерфейсов.
  • Compose Multiplatform работает на настольных компьютерах Android, macOS, Windows и Linux, а также в Интернете.
  • Настольные приложения могут иметь несколько окон.
  • Настольные приложения могут использовать систему меню.
  • Android также может использовать Compose Multiplatform.

Куда идти дальше?

Создание рабочего стола:

Совместные предприятия Azul: https://www.azul.com/downloads /

Поздравляю! Вы написали мультиплатформенное приложение Compose, которое использует общую библиотеку для бизнес-логики. Похоже, вы на пути к освоению всех различных платформ, которые может предложить KMM.