Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dmitry-osin/efdadd2aea202adcb6dde28a7dab412b to your computer and use it in GitHub Desktop.
Save dmitry-osin/efdadd2aea202adcb6dde28a7dab412b to your computer and use it in GitHub Desktop.
Шпаргалка по Compose Multiplatform UI Framework

Шпаргалка по Compose Multiplatform UI Framework

Основные концепции Compose Multiplatform

Compose Multiplatform - это декларативный UI фреймворк от JetBrains, позволяющий создавать пользовательские интерфейсы для различных платформ (Android, iOS, Desktop, Web) с использованием языка Kotlin.

Ключевые особенности:

  • Декларативный подход: UI описывается как функция состояния
  • Многоплатформенность: один код для разных платформ
  • Реактивность: автоматическое обновление UI при изменении данных
  • Компонентный подход: переиспользуемые UI-компоненты

Базовая структура Compose-приложения

fun main() = application {
    Window(onCloseRequest = ::exitApplication, title = "Compose App") {
        App()
    }
}

@Composable
fun App() {
    MaterialTheme {
        Surface(modifier = Modifier.fillMaxSize()) {
            // Содержимое приложения
            Text("Привет, Compose Multiplatform!")
        }
    }
}

Основные UI-элементы

Text - Текст

@Composable
fun TextExample() {
    Column {
        Text("Обычный текст")
        
        Text(
            text = "Стилизованный текст",
            color = Color.Blue,
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold,
            textAlign = TextAlign.Center,
            modifier = Modifier.fillMaxWidth()
        )
        
        Text(
            text = "Текст с переносами и стилями",
            style = TextStyle(
                fontFamily = FontFamily.Monospace,
                lineHeight = 24.sp
            )
        )
    }
}

Button - Кнопка

@Composable
fun ButtonsExample() {
    Column(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Обычная кнопка
        Button(onClick = { println("Нажата кнопка") }) {
            Text("Обычная кнопка")
        }
        
        // Кнопка с иконкой
        Button(onClick = { /* действие */ }) {
            Icon(
                Icons.Default.Favorite,
                contentDescription = "Избранное",
                modifier = Modifier.size(18.dp)
            )
            Spacer(Modifier.width(8.dp))
            Text("Кнопка с иконкой")
        }
        
        // Контурная кнопка
        OutlinedButton(onClick = { /* действие */ }) {
            Text("Контурная кнопка")
        }
        
        // Текстовая кнопка
        TextButton(onClick = { /* действие */ }) {
            Text("Текстовая кнопка")
        }
    }
}

Image - Изображение

@Composable
fun ImageExample() {
    Column {
        // Изображение из ресурсов
        Image(
            painter = painterResource("sample.png"),
            contentDescription = "Пример изображения",
            modifier = Modifier.size(100.dp),
            contentScale = ContentScale.Crop
        )
        
        // Изображение из векторной иконки
        Image(
            imageVector = Icons.Default.Person,
            contentDescription = "Иконка пользователя",
            modifier = Modifier.size(50.dp)
        )
    }
}

TextField - Поле ввода

@Composable
fun TextFieldExample() {
    var text by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    
    Column(modifier = Modifier.padding(16.dp)) {
        // Обычное поле ввода
        TextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Введите текст") },
            modifier = Modifier.fillMaxWidth()
        )
        
        Spacer(modifier = Modifier.height(8.dp))
        
        // Контурное поле ввода
        OutlinedTextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Контурное поле") },
            modifier = Modifier.fillMaxWidth()
        )
        
        Spacer(modifier = Modifier.height(8.dp))
        
        // Поле для ввода пароля
        OutlinedTextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("Пароль") },
            visualTransformation = PasswordVisualTransformation(),
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
            modifier = Modifier.fillMaxWidth()
        )
    }
}

Row и Column - Контейнеры для размещения элементов

@Composable
fun LayoutExample() {
    // Вертикальное расположение (столбец)
    Column(
        modifier = Modifier.fillMaxWidth().padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Элемент 1 в столбце")
        Text("Элемент 2 в столбце")
        Text("Элемент 3 в столбце")
        
        // Горизонтальное расположение (строка)
        Row(
            modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
            horizontalArrangement = Arrangement.SpaceEvenly,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text("Элемент 1 в строке")
            Text("Элемент 2 в строке")
            Text("Элемент 3 в строке")
        }
    }
}

Box - Контейнер для наложения элементов

@Composable
fun BoxExample() {
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(Color.LightGray)
    ) {
        // Элемент в левом верхнем углу
        Text(
            "Верхний левый",
            modifier = Modifier.align(Alignment.TopStart).padding(8.dp)
        )
        
        // Элемент по центру
        Box(
            modifier = Modifier
                .size(100.dp)
                .background(Color.White)
                .align(Alignment.Center),
            contentAlignment = Alignment.Center
        ) {
            Text("Центр")
        }
        
        // Элемент в правом нижнем углу
        Text(
            "Нижний правый",
            modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp)
        )
    }
}

LazyColumn и LazyRow - Прокручиваемые списки

@Composable
fun LazyListExample() {
    Column {
        // Вертикальный список (аналог RecyclerView)
        LazyColumn(
            modifier = Modifier.height(200.dp),
            verticalArrangement = Arrangement.spacedBy(4.dp),
            contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
        ) {
            items(50) { index ->
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    elevation = 2.dp
                ) {
                    Text(
                        "Элемент списка $index",
                        modifier = Modifier.padding(16.dp)
                    )
                }
            }
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Горизонтальный список
        LazyRow(
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            contentPadding = PaddingValues(horizontal = 16.dp)
        ) {
            items(20) { index ->
                Card(
                    modifier = Modifier.size(100.dp),
                    elevation = 2.dp
                ) {
                    Box(contentAlignment = Alignment.Center) {
                        Text("Элемент $index")
                    }
                }
            }
        }
    }
}

Checkbox, RadioButton, Switch - Элементы выбора

@Composable
fun SelectionControlsExample() {
    var checkedState by remember { mutableStateOf(false) }
    var selectedOption by remember { mutableStateOf(0) }
    var switchState by remember { mutableStateOf(false) }
    
    Column(modifier = Modifier.padding(16.dp)) {
        // Checkbox
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier.clickable { checkedState = !checkedState }
        ) {
            Checkbox(
                checked = checkedState,
                onCheckedChange = { checkedState = it }
            )
            Text("Флажок выбора", modifier = Modifier.padding(start = 8.dp))
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // RadioButton
        val radioOptions = listOf("Вариант 1", "Вариант 2", "Вариант 3")
        radioOptions.forEachIndexed { index, text ->
            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier
                    .clickable { selectedOption = index }
                    .padding(vertical = 4.dp)
            ) {
                RadioButton(
                    selected = selectedOption == index,
                    onClick = { selectedOption = index }
                )
                Text(text, modifier = Modifier.padding(start = 8.dp))
            }
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Switch
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier.clickable { switchState = !switchState }
        ) {
            Switch(
                checked = switchState,
                onCheckedChange = { switchState = it }
            )
            Text("Переключатель", modifier = Modifier.padding(start = 8.dp))
        }
    }
}

Управление состоянием

Основные типы состояний

@Composable
fun StateExample() {
    // Простое состояние
    var counter by remember { mutableStateOf(0) }
    
    // Состояние с несколькими полями
    var formState by remember {
        mutableStateOf(FormState(name = "", email = "", age = 0))
    }
    
    // Список как состояние
    var items by remember { mutableStateOf(listOf("Элемент 1", "Элемент 2")) }
    
    Column(modifier = Modifier.padding(16.dp)) {
        // Пример использования простого состояния
        Text("Счетчик: $counter")
        Button(onClick = { counter++ }) {
            Text("Увеличить")
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Пример использования состояния формы
        OutlinedTextField(
            value = formState.name,
            onValueChange = { formState = formState.copy(name = it) },
            label = { Text("Имя") }
        )
        
        OutlinedTextField(
            value = formState.email,
            onValueChange = { formState = formState.copy(email = it) },
            label = { Text("Email") }
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Пример использования списка как состояния
        Button(onClick = {
            items = items + "Элемент ${items.size + 1}"
        }) {
            Text("Добавить элемент")
        }
        
        LazyColumn {
            items(items) { item ->
                Text(item, modifier = Modifier.padding(vertical = 4.dp))
            }
        }
    }
}

// Класс для состояния формы
data class FormState(
    val name: String,
    val email: String,
    val age: Int
)

Подъем состояния (State Hoisting)

@Composable
fun StateHoistingExample() {
    // Состояние поднято на уровень родительского компонента
    var text by remember { mutableStateOf("") }
    
    Column(modifier = Modifier.padding(16.dp)) {
        // Передаем состояние и обработчик изменений дочернему компоненту
        CustomTextField(
            value = text,
            onValueChange = { text = it },
            label = "Введите текст"
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Text("Введенный текст: $text")
    }
}

@Composable
fun CustomTextField(
    value: String,
    onValueChange: (String) -> Unit,
    label: String
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text(label) },
        modifier = Modifier.fillMaxWidth()
    )
}

Запоминание вычислений (Derived State)

@Composable
fun DerivedStateExample() {
    var text by remember { mutableStateOf("") }
    
    // Производное состояние - вычисляется на основе другого состояния
    val isTextValid by remember(text) {
        derivedStateOf { text.length >= 3 }
    }
    
    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Введите минимум 3 символа") },
            modifier = Modifier.fillMaxWidth(),
            isError = !isTextValid && text.isNotEmpty()
        )
        
        Spacer(modifier = Modifier.height(8.dp))
        
        if (!isTextValid && text.isNotEmpty()) {
            Text(
                "Текст должен содержать минимум 3 символа",
                color = MaterialTheme.colors.error,
                style = MaterialTheme.typography.caption
            )
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(
            onClick = { /* действие */ },
            enabled = isTextValid
        ) {
            Text("Отправить")
        }
    }
}

Модификаторы (Modifier)

Модификаторы - это ключевой инструмент для настройки внешнего вида и поведения компонентов.

@Composable
fun ModifiersExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        // Базовые модификаторы размера
        Box(
            modifier = Modifier
                .size(100.dp)
                .background(Color.Red)
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Комбинирование модификаторов
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color.Blue)
                .padding(8.dp)
                .border(2.dp, Color.White)
        ) {
            Text(
                "Текст с модификаторами",
                color = Color.White,
                modifier = Modifier.align(Alignment.Center)
            )
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Модификаторы для интерактивности
        Box(
            modifier = Modifier
                .size(100.dp)
                .background(Color.Green)
                .clickable { println("Клик по зеленому квадрату") }
                .padding(8.dp)
        ) {
            Text(
                "Кликабельная область",
                color = Color.White,
                textAlign = TextAlign.Center,
                modifier = Modifier.align(Alignment.Center)
            )
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Модификаторы для анимации
        var expanded by remember { mutableStateOf(false) }
        Box(
            modifier = Modifier
                .animateContentSize()
                .background(Color.LightGray)
                .clickable { expanded = !expanded }
                .height(if (expanded) 200.dp else 50.dp)
                .fillMaxWidth()
        ) {
            Text(
                if (expanded) "Нажмите, чтобы свернуть" else "Нажмите, чтобы развернуть",
                modifier = Modifier
                    .align(Alignment.Center)
                    .padding(8.dp)
            )
        }
    }
}

Темы и стилизация

Основная тема Material

@Composable
fun ThemeExample() {
    // Создание собственной темы
    MaterialTheme(
        colors = lightColors(
            primary = Color(0xFF6200EE),
            primaryVariant = Color(0xFF3700B3),
            secondary = Color(0xFF03DAC6)
        ),
        typography = Typography(
            body1 = TextStyle(
                fontFamily = FontFamily.Default,
                fontWeight = FontWeight.Normal,
                fontSize = 16.sp
            ),
            h1 = TextStyle(
                fontFamily = FontFamily.Default,
                fontWeight = FontWeight.Bold,
                fontSize = 30.sp
            )
        ),
        shapes = Shapes(
            small = RoundedCornerShape(4.dp),
            medium = RoundedCornerShape(8.dp),
            large = RoundedCornerShape(12.dp)
        )
    ) {
        // Контент, использующий тему
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                "Заголовок с темой",
                style = MaterialTheme.typography.h1,
                color = MaterialTheme.colors.primary
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Surface(
                color = MaterialTheme.colors.surface,
                elevation = 4.dp,
                shape = MaterialTheme.shapes.medium
            ) {
                Text(
                    "Текст на поверхности с темой",
                    modifier = Modifier.padding(16.dp),
                    style = MaterialTheme.typography.body1
                )
            }
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Button(
                onClick = { /* действие */ },
                colors = ButtonDefaults.buttonColors(
                    backgroundColor = MaterialTheme.colors.secondary
                ),
                shape = MaterialTheme.shapes.small
            ) {
                Text("Кнопка с темой")
            }
        }
    }
}

Темная и светлая темы

@Composable
fun DarkLightThemeExample() {
    var isDarkTheme by remember { mutableStateOf(false) }
    
    // Переключение между темной и светлой темами
    MaterialTheme(
        colors = if (isDarkTheme) darkColors() else lightColors()
    ) {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colors.background
        ) {
            Column(
                modifier = Modifier.padding(16.dp),
                verticalArrangement = Arrangement.spacedBy(16.dp)
            ) {
                Text(
                    "Пример ${if (isDarkTheme) "темной" else "светлой"} темы",
                    style = MaterialTheme.typography.h5
                )
                
                Switch(
                    checked = isDarkTheme,
                    onCheckedChange = { isDarkTheme = it }
                )
                
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    elevation = 4.dp
                ) {
                    Column(modifier = Modifier.padding(16.dp)) {
                        Text(
                            "Карточка с темой",
                            style = MaterialTheme.typography.h6
                        )
                        
                        Spacer(modifier = Modifier.height(8.dp))
                        
                        Text(
                            "Текст адаптируется к выбранной теме автоматически"
                        )
                    }
                }
                
                Button(onClick = { /* действие */ }) {
                    Text("Кнопка с темой")
                }
                
                OutlinedTextField(
                    value = "",
                    onValueChange = {},
                    label = { Text("Поле ввода с темой") }
                )
            }
        }
    }
}

Анимации

Базовая анимация

@Composable
fun BasicAnimationExample() {
    var expanded by remember { mutableStateOf(false) }
    
    // Анимируемые значения
    val backgroundColor by animateColorAsState(
        if (expanded) Color.Green else Color.LightGray
    )
    
    val size by animateDpAsState(
        if (expanded) 200.dp else 100.dp
    )
    
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = Modifier
                .size(size)
                .background(backgroundColor)
                .clickable { expanded = !expanded },
            contentAlignment = Alignment.Center
        ) {
            Text(
                if (expanded) "Нажмите, чтобы уменьшить" else "Нажмите, чтобы увеличить",
                textAlign = TextAlign.Center
            )
        }
    }
}

Анимация контента

@Composable
fun ContentAnimationExample() {
    var currentPage by remember { mutableStateOf(0) }
    
    Column(modifier = Modifier.padding(16.dp)) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            Button(onClick = { currentPage = 0 }) {
                Text("Страница 1")
            }
            
            Button(onClick = { currentPage = 1 }) {
                Text("Страница 2")
            }
            
            Button(onClick = { currentPage = 2 }) {
                Text("Страница 3")
            }
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Анимация при смене контента
        AnimatedContent(
            targetState = currentPage,
            transitionSpec = {
                fadeIn() + slideInHorizontally() with
                fadeOut() + slideOutHorizontally()
            }
        ) { page ->
            when (page) {
                0 -> PageContent("Содержимое первой страницы")
                1 -> PageContent("Содержимое второй страницы")
                2 -> PageContent("Содержимое третьей страницы")
            }
        }
    }
}

@Composable
fun PageContent(text: String) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(200.dp)
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = text,
            fontSize = 20.sp,
            textAlign = TextAlign.Center
        )
    }
}

Особенности для разных платформ

Общий код

@Composable
fun MultiplatformExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Этот код работает на всех платформах")
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Общие компоненты
        Button(onClick = { /* действие */ }) {
            Text("Кнопка для всех платформ")
        }
    }
}

Платформенно-специфичный код

@Composable
fun PlatformSpecificExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Базовый контент для всех платформ")
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Платформенно-специфичный код
        when (getPlatform()) {
            Platform.ANDROID -> AndroidSpecificContent()
            Platform.IOS -> IOSSpecificContent()
            Platform.DESKTOP -> DesktopSpecificContent()
            Platform.WEB -> WebSpecificContent()
        }
    }
}

// Платформенно-специфичные функции
@Composable
expect fun AndroidSpecificContent()

@Composable
expect fun IOSSpecificContent()

@Composable
expect fun DesktopSpecificContent()

@Composable
expect fun WebSpecificContent()

// Реализации для Android
@Composable
actual fun AndroidSpecificContent() {
    Text("Контент специфичный для Android")
    // Android-специфичные компоненты
}

// Реализации для Desktop
@Composable
actual fun DesktopSpecificContent() {
    Text("Контент специфичный для Desktop")
    // Desktop-специфичные компоненты
}

// И так далее для других платформ

Навигация

Простая навигация

@Composable
fun NavigationExample() {
    var currentScreen by remember { mutableStateOf("home") }
    
    Column {
        // Навигационная панель
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.LightGray)
                .padding(8.dp),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            NavigationButton(
                text = "Главная",
                selected = currentScreen == "home",
                onClick = { currentScreen = "home" }
            )
            
            NavigationButton(
                text = "Профиль",
                selected = currentScreen == "profile",
                onClick = { currentScreen = "profile" }
            )
            
            NavigationButton(
                text = "Настройки",
                selected = currentScreen == "settings",
                onClick = { currentScreen = "settings" }
            )
        }
        
        // Контент экрана
        Box(modifier = Modifier.fillMaxSize()) {
            when (currentScreen) {
                "home" -> HomeScreen()
                "profile" -> ProfileScreen()
                "settings" -> SettingsScreen()
            }
        }
    }
}

@Composable
fun NavigationButton(
    text: String,
    selected: Boolean,
    onClick: () -> Unit
) {
    Button(
        onClick = onClick,
        colors = ButtonDefaults.buttonColors(
            backgroundColor = if (selected) Color.Blue else Color.Gray
        )
    ) {
        Text(text, color = Color.White)
    }
}

@Composable
fun HomeScreen() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text("Экран главной страницы", fontSize = 24.sp)
    }
}

@Composable
fun ProfileScreen() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text("Экран профиля", fontSize = 24.sp)
    }
}

@Composable
fun SettingsScreen() {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text("Экран настроек", fontSize = 24.sp)
    }
}

Жесты и взаимодействие

@Composable
fun GesturesExample() {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }
    var scale by remember { mutableStateOf(1f) }
    var rotation by remember { mutableStateOf(0f) }
    
    Column(modifier = Modifier.padding(16.dp)) {
        // Область для перетаскивания
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(300.dp)
                .background(Color.LightGray)
                .padding(16.dp)
        ) {
            // Перетаскиваемый элемент
            Box(
                modifier = Modifier
                    .size(100.dp)
                    .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                    .scale(scale)
                    .rotate(rotation)
                    .background(Color.Blue)
                    // Обработка перетаскивания
                    .pointerInput(Unit) {
                        detectDragGestures { change, dragAmount ->
                            change.consume()
                            offsetX += dragAmount.x
                            offsetY += dragAmount.y
                        }
                    }
                    // Обработка масштабирования
                    .pointerInput(Unit) {
                        detectTransformGestures { _, _, zoom, rotationChange ->
                            scale *= zoom
                            rotation += rotationChange
                        }
                    }
            )
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Кнопка для сброса
        Button(
            onClick = {
                offsetX = 0f
                offsetY = 0f
                scale = 1f
                rotation = 0f
            },
            modifier = Modifier.align(Alignment.CenterHorizontally)
        ) {
            Text("Сбросить")
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Информация о текущем состоянии
        Text("Смещение: X=${offsetX.roundToInt()}, Y=${offsetY.roundToInt()}")
        Text("Масштаб: ${String.format("%.2f", scale)}")
        Text("Поворот: ${String.format("%.1f", rotation)}°")
    }
}

Продолжаю шпаргалку по Compose Multiplatform UI Framework, раздел "Эффекты побочных действий":

Эффекты побочных действий

@Composable
fun SideEffectsExample() {
    var count by remember { mutableStateOf(0) }
    
    // LaunchedEffect - выполняется при первой композиции и при изменении ключа
    LaunchedEffect(key1 = count) {
        println("LaunchedEffect: count изменился на $count")
        
        // Можно выполнять корутины
        delay(1000)
        println("Прошла 1 секунда после изменения count")
    }
    
    // DisposableEffect - выполняется при первой композиции и очищается при уходе со страницы
    DisposableEffect(Unit) {
        println("DisposableEffect: компонент добавлен в композицию")
        
        // Возвращаем действие для очистки
        onDispose {
            println("DisposableEffect: компонент удален из композиции")
        }
    }
    
    // SideEffect - выполняется при каждой успешной рекомпозиции
    SideEffect {
        println("SideEffect: произошла рекомпозиция")
    }
    
    // rememberCoroutineScope - сохраняет область корутин, привязанную к композиции
    val coroutineScope = rememberCoroutineScope()
    
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Счетчик: $count")
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = { count++ }) {
            Text("Увеличить счетчик")
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Использование coroutineScope для запуска корутин вне композиции
        Button(
            onClick = {
                coroutineScope.launch {
                    // Действия в корутине
                    println("Корутина запущена")
                    delay(2000)
                    println("Корутина завершена через 2 секунды")
                }
            }
        ) {
            Text("Запустить корутину")
        }
    }
}

Эффект для работы с жизненным циклом

@Composable
fun LifecycleAwareExample() {
    // Получение текущего состояния жизненного цикла
    val lifecycleOwner = LocalLifecycleOwner.current
    val lifecycle = lifecycleOwner.lifecycle
    
    // Отслеживание изменений жизненного цикла
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_CREATE -> println("ON_CREATE")
                Lifecycle.Event.ON_START -> println("ON_START")
                Lifecycle.Event.ON_RESUME -> println("ON_RESUME")
                Lifecycle.Event.ON_PAUSE -> println("ON_PAUSE")
                Lifecycle.Event.ON_STOP -> println("ON_STOP")
                Lifecycle.Event.ON_DESTROY -> println("ON_DESTROY")
                else -> println("Другое событие: $event")
            }
        }
        
        lifecycle.addObserver(observer)
        
        onDispose {
            lifecycle.removeObserver(observer)
        }
    }
    
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text("Компонент с отслеживанием жизненного цикла")
    }
}

Продвинутые темы

Композиция и рекомпозиция

@Composable
fun RecompositionExample() {
    var counter by remember { mutableStateOf(0) }
    
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Счетчик: $counter")
        
        // Этот компонент будет перекомпозирован при изменении counter
        Text("Этот текст будет перекомпозирован: ${System.currentTimeMillis()}")
        
        // Этот компонент НЕ будет перекомпозирован при изменении counter
        val stableText = remember {
            "Этот текст стабилен: ${System.currentTimeMillis()}"
        }
        Text(stableText)
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = { counter++ }) {
            Text("Увеличить счетчик")
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Оптимизация с помощью key
        key(counter / 5) {
            Text("Этот текст обновляется каждые 5 нажатий: ${System.currentTimeMillis()}")
        }
    }
}

Кастомные компоненты

@Composable
fun CustomComponentsExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        // Использование кастомного компонента
        CustomCard(
            title = "Заголовок карточки",
            content = "Содержимое карточки с подробным описанием",
            onButtonClick = { println("Кнопка нажата") }
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Еще один кастомный компонент
        RatingBar(
            rating = 3.5f,
            maxRating = 5,
            onRatingChanged = { println("Новый рейтинг: $it") }
        )
    }
}

// Кастомная карточка
@Composable
fun CustomCard(
    title: String,
    content: String,
    onButtonClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Card(
        modifier = modifier.fillMaxWidth(),
        elevation = 4.dp
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(
                text = title,
                style = MaterialTheme.typography.h6
            )
            
            Text(
                text = content,
                style = MaterialTheme.typography.body2
            )
            
            Button(
                onClick = onButtonClick,
                modifier = Modifier.align(Alignment.End)
            ) {
                Text("Действие")
            }
        }
    }
}

// Кастомный компонент рейтинга
@Composable
fun RatingBar(
    rating: Float,
    maxRating: Int = 5,
    onRatingChanged: (Float) -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.spacedBy(4.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        for (i in 1..maxRating) {
            val isFilled = i <= rating
            val isHalfFilled = !isFilled && i <= rating + 0.5f
            
            Icon(
                imageVector = if (isHalfFilled) Icons.Default.StarHalf
                             else if (isFilled) Icons.Default.Star
                             else Icons.Default.StarBorder,
                contentDescription = "Звезда $i",
                tint = Color.Gold,
                modifier = Modifier
                    .size(24.dp)
                    .clickable { onRatingChanged(i.toFloat()) }
            )
        }
        
        Text(
            text = String.format("%.1f", rating),
            modifier = Modifier.padding(start = 8.dp)
        )
    }
}

Адаптивный дизайн

@Composable
fun AdaptiveLayoutExample() {
    // Определение текущего размера экрана
    val windowInfo = rememberWindowInfo()
    
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            "Текущий размер экрана: ${windowInfo.screenWidthInfo}",
            style = MaterialTheme.typography.h6
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Адаптивный макет в зависимости от размера экрана
        when (windowInfo.screenWidthInfo) {
            WindowInfo.WindowType.Compact -> {
                // Макет для узких экранов (телефоны)
                CompactLayout()
            }
            WindowInfo.WindowType.Medium -> {
                // Макет для средних экранов (планшеты)
                MediumLayout()
            }
            WindowInfo.WindowType.Expanded -> {
                // Макет для широких экранов (десктоп)
                ExpandedLayout()
            }
        }
    }
}

// Вспомогательный класс для определения размера экрана
class WindowInfo(
    val screenWidthInfo: WindowType,
    val screenHeightInfo: WindowType
) {
    sealed class WindowType {
        object Compact : WindowType()
        object Medium : WindowType()
        object Expanded : WindowType()
    }
}

@Composable
fun rememberWindowInfo(): WindowInfo {
    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp.dp
    val screenHeight = configuration.screenHeightDp.dp
    
    return remember(screenWidth, screenHeight) {
        WindowInfo(
            screenWidthInfo = when {
                screenWidth < 600.dp -> WindowInfo.WindowType.Compact
                screenWidth < 840.dp -> WindowInfo.WindowType.Medium
                else -> WindowInfo.WindowType.Expanded
            },
            screenHeightInfo = when {
                screenHeight < 480.dp -> WindowInfo.WindowType.Compact
                screenHeight < 900.dp -> WindowInfo.WindowType.Medium
                else -> WindowInfo.WindowType.Expanded
            }
        )
    }
}

@Composable
fun CompactLayout() {
    // Вертикальный макет для узких экранов
    Column(
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        repeat(3) { index ->
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = 4.dp
            ) {
                Text(
                    "Элемент $index для компактного экрана",
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
    }
}

@Composable
fun MediumLayout() {
    // Сетка 2x2 для средних экранов
    Column(
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Card(
                modifier = Modifier.weight(1f),
                elevation = 4.dp
            ) {
                Text(
                    "Элемент 1 для среднего экрана",
                    modifier = Modifier.padding(16.dp)
                )
            }
            
            Card(
                modifier = Modifier.weight(1f),
                elevation = 4.dp
            ) {
                Text(
                    "Элемент 2 для среднего экрана",
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
        
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Card(
                modifier = Modifier.weight(1f),
                elevation = 4.dp
            ) {
                Text(
                    "Элемент 3 для среднего экрана",
                    modifier = Modifier.padding(16.dp)
                )
            }
            
            Card(
                modifier = Modifier.weight(1f),
                elevation = 4.dp
            ) {
                Text(
                    "Элемент 4 для среднего экрана",
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
    }
}

@Composable
fun ExpandedLayout() {
    // Горизонтальный макет для широких экранов
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        repeat(3) { index ->
            Card(
                modifier = Modifier.weight(1f),
                elevation = 4.dp
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Text(
                        "Элемент $index для широкого экрана",
                        style = MaterialTheme.typography.h6
                    )
                    
                    Spacer(modifier = Modifier.height(8.dp))
                    
                    Text(
                        "Дополнительная информация для широкого экрана"
                    )
                }
            }
        }
    }
}

Интеграция с другими библиотеками

Интеграция с ViewModel

// ViewModel для примера
class ExampleViewModel : ViewModel() {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> = _counter.asStateFlow()
    
    private val _items = MutableStateFlow<List<String>>(emptyList())
    val items: StateFlow<List<String>> = _items.asStateFlow()
    
    fun incrementCounter() {
        _counter.value++
    }
    
    fun addItem(item: String) {
        _items.value = _items.value + item
    }
}

@Composable
fun ViewModelIntegrationExample() {
    // Создание или получение ViewModel
    val viewModel = viewModel<ExampleViewModel>()
    
    // Наблюдение за состоянием из ViewModel
    val counter by viewModel.counter.collectAsState()
    val items by viewModel.items.collectAsState()
    
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            "Счетчик: $counter",
            style = MaterialTheme.typography.h5
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = { viewModel.incrementCounter() }) {
            Text("Увеличить счетчик")
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        var newItem by remember { mutableStateOf("") }
        
        Row(
            modifier = Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            OutlinedTextField(
                value = newItem,
                onValueChange = { newItem = it },
                label = { Text("Новый элемент") },
                modifier = Modifier.weight(1f)
            )
            
            Spacer(modifier = Modifier.width(8.dp))
            
            Button(
                onClick = {
                    if (newItem.isNotEmpty()) {
                        viewModel.addItem(newItem)
                        newItem = ""
                    }
                }
            ) {
                Text("Добавить")
            }
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        LazyColumn {
            items(items) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 8.dp)
                )
                Divider()
            }
        }
    }
}

Интеграция с Coroutines Flow

@Composable
fun FlowIntegrationExample() {
    // Создание Flow
    val countDownFlow = flow {
        val startingValue = 10
        var currentValue = startingValue
        emit(startingValue)
        
        while (currentValue > 0) {
            delay(1000)
            currentValue--
            emit(currentValue)
        }
    }
    
    // Сбор значений из Flow
    val countDown by countDownFlow.collectAsState(initial = 10)
    
    // Создание Flow с обновлениями времени
    val timeFlow = flow {
        while (true) {
            emit(System.currentTimeMillis())
            delay(1000)
        }
    }
    
    // Форматирование времени
    val dateFormat = remember { SimpleDateFormat("HH:mm:ss", Locale.getDefault()) }
    val currentTime by timeFlow.collectAsState(initial = System.currentTimeMillis())
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            "Обратный отсчет: $countDown",
            style = MaterialTheme.typography.h3
        )
        
        Spacer(modifier = Modifier.height(32.dp))
        
        Text(
            "Текущее время: ${dateFormat.format(Date(currentTime))}",
            style = MaterialTheme.typography.h5
        )
    }
}

Полезные советы и лучшие практики

Оптимизация производительности

@Composable
fun PerformanceTipsExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            "Советы по оптимизации производительности:",
            style = MaterialTheme.typography.h6
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // 1. Используйте remember для кэширования вычислений
        val expensiveComputation = remember {
            // Здесь может быть дорогостоящее вычисление
            "Результат дорогостоящего вычисления"
        }
        Text("1. Кэшированный результат: $expensiveComputation")
        
        Spacer(modifier = Modifier.height(8.dp))
        
        // 2. Используйте derivedStateOf для вычисляемых состояний
        var list by remember { mutableStateOf(List(100) { it }) }
        val filteredList by remember(list) {
            derivedStateOf { list.filter { it % 2 == 0 } }
        }
        Text("2. Размер отфильтрованного списка: ${filteredList.size}")
        
        Spacer(modifier = Modifier.height(8.dp))
        
        // 3. Используйте key для контроля рекомпозиции
        var counter by remember { mutableStateOf(0) }
        key(counter / 10) {
            Text("3. Этот текст обновляется каждые 10 изменений: $counter")
        }
        
        Spacer(modifier = Modifier.height(8.dp))
        
        // 4. Выносите стабильные компоненты
        StableComponent()
        
        Spacer(modifier = Modifier.height(8.dp))
        
        // 5. Используйте LazyColumn вместо Column для больших списков
        Text("5. Для больших списков используйте LazyColumn вместо Column")
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = { counter++ }) {
            Text("Увеличить счетчик")
        }
    }
}

// Стабильный компонент, который не будет перекомпозирован при изменении родителя
@Composable
fun StableComponent() {
    Text(
        "4. Стабильный компонент (не перекомпозируется)",
        modifier = Modifier
            .fillMaxWidth()
            .background(Color.LightGray)
            .padding(8.dp)
    )
}

Отладка Compose

@Composable
fun DebuggingExample() {
    // Включение отладочной информации
    CompositionLocalProvider(LocalInspectionMode provides true) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                "Советы по отладке Compose:",
                style = MaterialTheme.typography.h6
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            // 1. Использование модификатора debugInspectorInfo
            Box(
                modifier = Modifier
                    .size(100.dp)
                    .background(Color.Red)
                    .debugInspectorInfo {
                        name = "Красный квадрат"
                        properties["size"] = "100.dp"
                        properties["color"] = "Red"
                    }
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            // 2. Логирование при рекомпозиции
            var counter by remember { mutableStateOf(0) }
            
            if (counter % 2 == 0) {
                Text("Счетчик четный: $counter")
                SideEffect {
                    println("Рекомпозиция: счетчик четный - $counter")
                }
            } else {
                Text("Счетчик нечетный: $counter")
                SideEffect {
                    println("Рекомпозиция: счетчик нечетный - $counter")
                }
            }
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Button(onClick = { counter++ }) {
                Text("Увеличить счетчик")
            }
        }
    }
}

Заключение

Compose Multiplatform - мощный и гибкий фреймворк для создания пользовательских интерфейсов на различных платформах. Ключевые аспекты, которые стоит запомнить:

  1. Декларативный подход - UI описывается как функция состояния, что делает код более предсказуемым и легким в поддержке.

  2. Компонентный подход - создавайте переиспользуемые компоненты для повышения эффективности разработки.

  3. Управление состоянием - используйте remember, mutableStateOf, derivedStateOf для эффективного управления состоянием.

  4. Модификаторы - цепочка модификаторов позволяет гибко настраивать внешний вид и поведение компонентов.

  5. Эффекты - используйте LaunchedEffect, DisposableEffect, SideEffect для управления побочными эффектами.

  6. Оптимизация производительности - применяйте кэширование, ключи и другие техники для оптимизации.

  7. Многоплатформенность - используйте общий код где возможно, и платформенно-специфичный код где необходимо.

Эта шпаргалка охватывает основные аспекты Compose Multiplatform, но фреймворк постоянно развивается, поэтому рекомендуется следить за официальной документацией JetBrains для получения актуальной информации.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment