UI Components

https://developer.android.com/develop/ui/compose/layouts/flow
https://developer.android.com/develop/ui/compose/layouts/pager
https://developer.android.com/develop/ui/compose/layouts/constraintlayout
Components
Text

Отображает текст на экране.

Text(text = "Hello, Compose!")

Текст фиксированной высоты c расположением center_vertical

Text(
    text = stringResource(R.string.text),
    modifier = Modifier
        .constrainAs(anyText) {
            width = Dimension.wrapContent
            height = Dimension.value(48.dp)
            start.linkTo(parent.start)
            top.linkTo(parent.top)
        }
        .wrapContentHeight(Alignment.CenterVertically)
)

Enforcing constraints. Текст сокращается в зависимости от контента рядом

Row {
    Text(
        text = "Text",
        overflow = TextOverflow.Ellipsis,
        maxLines = 1,
        modifier = Modifier.weight(weight = 1F, fill = false)
    )

    Icon(
        painter = painterResource(R.drawable.ic_icon),
        contentDescription = null
    )
}
BasicText

Базовый компонент для отображения текста.

BasicText("Hello, World!")
ClickableText

Текст, который реагирует на клики.

ClickableText(
    text = AnnotatedString("Click me"),
    onClick = { offset -> /* handle click */ }
)
Button

Кнопка, которая выполняет действие при нажатии.

Button(
    onClick = { /* Handle click */ }
) {
    Text(text = "Button")
}
Preview
TextButton

Кнопка с текстом, без фона и границ.

TextButton(
    onClick = { /* Handle click */ }
) {
    Text(text = "Text Button")
}
Preview
OutlinedButton

Кнопка с контуром вместо заполнения.

OutlinedButton(
    onClick = { /* Handle click */ }
) {
    Text(text = "Outlined Button")
}
Preview
IconButton

Кнопка, которая содержит только иконку.

IconButton(
    onClick = { /* Handle click */ }
) {
    Icon(
        imageVector = Icons.Default.Favorite,
        contentDescription = "Favorite"
    )
}
Preview
FilledIconButton

Кнопка с иконкой и заполненным фоном.

FilledIconButton(
    onClick = { /* Handle click */ }
) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = "Favorite"
    )
}
Preview
FilledTonalIconButton

Кнопка с иконкой и менее ярким фоном.

FilledTonalIconButton(
    onClick = { /* Handle click */ }
) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = "Favorite"
    )
}
Preview
OutlinedIconButton

Кнопка с иконкой и обводкой.

OutlinedIconButton(
    onClick = { /* Handle click */ }
) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = "Favorite"
    )
}
Preview
ElevatedButton

Кнопка с тенью.

ElevatedButton(
    onClick = { /* Handle click */ }
) {
    Text(text = "Elevated Button")
}
Preview
FilledTonalButton

Кнопка с заполненным, но менее ярким фоном.

FilledTonalButton(
    onClick = { /* Handle click */ }
) {
    Text(text = "Filled Tonal Button")
}
Preview
IconToggleButton

Кнопка-переключатель с иконкой.

var isChecked by remember { mutableStateOf(false) }

IconToggleButton(
    checked = isChecked,
    onCheckedChange = { isChecked = it }
) {
    val icon = if (isChecked) Icons.Default.Favorite else Icons.Default.FavoriteBorder
    val tint = if (isChecked) Color.Red else Color.Gray

    Icon(
        imageVector = icon,
        contentDescription = if (isChecked) "Удалить из избранного" else "Добавить в избранное",
        tint = tint
    )
}
Preview
FilledIconToggleButton

Кнопка-переключатель с заполненным фоном.

var isChecked by remember { mutableStateOf(false) }

FilledIconToggleButton(
    checked = isChecked,
    onCheckedChange = { isChecked = it }
) {
    val icon = if (isChecked) Icons.Default.Star else Icons.Default.StarBorder
    val tint = if (isChecked) Color.Yellow else Color.Gray

    Icon(
        imageVector = icon,
        contentDescription = if (isChecked) "Удалить из избранного" else "Добавить в избранное",
        tint = tint
    )
}
Preview
FilledTonalIconToggleButton

Кнопка-переключатель с менее ярким фоном.

var isChecked by remember { mutableStateOf(false) }

FilledTonalIconToggleButton(
    checked = isChecked,
    onCheckedChange = { isChecked = it }
) {
    val icon = if (isChecked) Icons.Default.Star else Icons.Default.StarBorder
    val tint = if (isChecked) Color.Yellow else Color.Gray

    Icon(
        imageVector = icon,
        contentDescription = if (isChecked) "Удалить из избранного" else "Добавить в избранное",
        tint = tint
    )
}
Preview
OutlinedIconToggleButton

Кнопка-переключатель с обводкой.

var isChecked by remember { mutableStateOf(false) }

OutlinedIconToggleButton(
    checked = isChecked,
    onCheckedChange = { isChecked = it }
) {
    val icon = if (isChecked) Icons.Default.Star else Icons.Default.StarBorder
    val tint = if (isChecked) Color.Yellow else Color.Gray

    Icon(
        imageVector = icon,
        contentDescription = if (isChecked) "Удалить из избранного" else "Добавить в избранное",
        tint = tint
    )
}
Preview
Image

Отображает изображение.

Image(
		painter = painterResource(id = R.drawable.image),
		contentDescription = "Example Image"
)
Icon

Отображает иконку, например из набора Material Icons.

Icon(imageVector = Icons.Default.Home, contentDescription = "Home Icon")
TextField

Поле для ввода текста.

var text by remember { mutableStateOf("Text") }

TextField(
    value = text,
    onValueChange = { newText -> text = newText },
    modifier = Modifier.fillMaxWidth(0.8F),
    textStyle = TextStyle(color = Color.Black, fontSize = 16.sp),
    label = { Text(text = "Введите текст") },
    placeholder = { Text(text = "Текст") }
)
Preview
Floating Suffix
class SuffixTransformation(
    private val suffix: String
): VisualTransformation {

    override fun filter(text: AnnotatedString): TransformedText {
        val result = text + AnnotatedString(
            text = suffix
        )

        val textWithSuffixMapping = object: OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                return offset
            }

            override fun transformedToOriginal(offset: Int): Int {
                if (text.isEmpty()) return 0
                if (offset >= text.length) return text.length
                return offset
            }
        }

        return TransformedText(result, textWithSuffixMapping )
    }
}

TextField(
    value = text,
    onValueChange = { value: String ->
        text = value
    },
    visualTransformation = if (text.isNotEmpty()) SuffixTransformation(suffix = "₽") else VisualTransformation.None
)
Password Field
var password by remember { mutableStateOf("") }
var passwordVisible by rememberSaveable { mutableStateOf(false) }

TextField(
    value = titleText,
    onValueChange = { value: String ->
        password = value
    },
    trailingIcon = {
        AnimatedVisibility(
            visible = password.isNotEmpty()
        ) {
            IconButton(
                onClick = {
                    passwordVisible = !passwordVisible
                }
            ) {
                Icon(
                    imageVector = if (passwordVisible) MoviesIcons.Visibility else MoviesIcons.VisibilityOff,
                    contentDescription = null
                )
            }
        }
    },
    visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation()
)
BasicTextField

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

var text by remember { mutableStateOf("Text") }

BasicTextField(
    value = text,
    onValueChange = { newText -> text = newText },
    modifier = Modifier.fillMaxWidth(0.8F),
    textStyle = TextStyle(color = Color.Black, fontSize = 36.sp),
    cursorBrush = SolidColor(Color.Blue)
)
Preview
OutlinedTextField

Текстовое поле с обводкой.

var text by remember { mutableStateOf("Text") }

OutlinedTextField(
    value = text,
    onValueChange = { newText -> text = newText },
    modifier = Modifier.fillMaxWidth(0.8F),
    textStyle = TextStyle(color = Color.Black, fontSize = 16.sp),
    label = { Text(text = "Введите текст") },
    placeholder = { Text(text = "Текст") }
)
Preview
Checkbox

Компонент для выбора «включено/выключено».

var checked by remember { mutableStateOf(false) }
Checkbox(
		checked = checked, 
		onCheckedChange = { checked = it }
)
Switch

Переключатель для изменения состояния (аналог чекбокса, но в стиле тумблера).

var switched by remember { mutableStateOf(false) }
Switch(
		checked = switched, 
		onCheckedChange = { switched = it }
)
Slider

Ползунок для выбора значения.

var sliderValue by remember { mutableStateOf(0f) }
Slider(
		value = sliderValue, 
		onValueChange = { sliderValue = it }
)
RangeSlider

Ползунок с диапазоном значений.

var range by remember { mutableStateOf(0f..100f) }
RangeSlider(values = range, onValueChange = { range = it })
InlineSlider

Ползунок для выбора значений.

InlineSlider(value = 0.5f, onValueChange = { /* Handle change */ })
RadioButton

Радио-кнопка для выбора одного элемента из группы.

var selected by remember { mutableStateOf(false) }
RadioButton(
		selected = selected, 
		onClick = { selected = !selected }
)
RadioGroup

Группа радиокнопок для выбора одного варианта.

Column {
    RadioButton(selected = true, onClick = { /* Действие */ })
    RadioButton(selected = false, onClick = { /* Действие */ })
}
Divider

Линия-разделитель между элементами.

Divider()
Spacer

Невидимый элемент, используемый для создания отступов между элементами.

Spacer(modifier = Modifier.height(16.dp))
CircularProgressIndicator

Индикатор выполнения в виде круга.

CircularProgressIndicator()
LinearProgressIndicator

Индикатор выполнения в виде полосы.

LinearProgressIndicator()
Card

Карточка с закругленными углами.

Card(elevation = 4.dp) {
    Text("This is a card")
}
ElevatedCard

Карточка с тенью.

ElevatedCard {
    Text("Elevated Card")
}
OutlinedCard

Карточка с обводкой.

OutlinedCard {
    Text("Outlined Card")
}
AppCard

Карточка для отображения информации.

AppCard {
    Text("App Card")
}
TitleCard

Карточка с заголовком.

TitleCard {
    Text("Title Card")
}
ClassicCard

Классическая карточка.

ClassicCard {
    Text("Classic Card")
}
WideClassicCard

Широкая классическая карточка.

WideClassicCard {
    Text("Wide Classic Card")
}
CompactCard

Компактная карточка.

CompactCard {
    Text("Compact Card")
}
SwipeToRevealCard

Карточка с функцией свайпа для раскрытия.

SwipeToRevealCard(
    swipeThreshold = 100.dp,
    onSwipe = { /* Handle swipe action */ }
) {
    Text("Swipe Me")
}
FloatingActionButton

Плавающая кнопка для важных действий.

FloatingActionButton(onClick = { /* Действие */ }) {
    Icon(Icons.Default.Add, contentDescription = "Add")
}
SmallFloatingActionButton

Небольшая плавающая кнопка действия.

SmallFloatingActionButton(onClick = { /* Handle click */ }) {
    Icon(Icons.Filled.Add, contentDescription = null)
}
LargeFloatingActionButton

Большая плавающая кнопка действия.

LargeFloatingActionButton(onClick = { /* Handle click */ }) {
    Icon(Icons.Filled.Add, contentDescription = null)
}
ExtendedFloatingActionButton

Расширенная версия FloatingActionButton с текстом.

ExtendedFloatingActionButton(
    text = { Text("Action") },
    onClick = { /* Handle click */ }
)
TopAppBar

Компонент для верхней панели приложения.

TopAppBar(title = { Text("App Title") })
BottomAppBar

Нижняя панель приложения, часто используется с FAB.

BottomAppBar {
    Text("Bottom App Bar")
}
BottomNavigation

Нижняя панель для навигации по приложению.

BottomNavigation {
    BottomNavigationItem(icon = { Icon(Icons.Default.Home, contentDescription = null) }, selected = false, onClick = { /* Действие */ })
}
NavigationDrawer

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

ModalDrawer(
    drawerContent = { Text("Drawer content") },
    content = { Text("Main content") }
)
TabRow

Компонент для создания вкладок.

var selectedTabIndex by remember { mutableStateOf(0) }
TabRow(selectedTabIndex = selectedTabIndex) {
    Tab(selected = selectedTabIndex == 0, onClick = { selectedTabIndex = 0 }) {
        Text("Tab 1")
    }
    Tab(selected = selectedTabIndex == 1, onClick = { selectedTabIndex = 1 }) {
        Text("Tab 2")
    }
}
ScrollableTabRow

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

ScrollableTabRow(selectedTabIndex = 0) {
    Text("Tab 1")
    Text("Tab 2")
}
PrimaryTabRow

Панель вкладок без прокрутки.

PrimaryTabRow(selectedTabIndex = 0) {
    Tab(selected = true, onClick = { /* Handle click */ }) {
        Text("Tab 1")
    }
}
PrimaryScrollableTabRow

Панель вкладок с возможностью прокрутки.

PrimaryScrollableTabRow(selectedTabIndex = 0) {
    Tab(selected = true, onClick = { /* Handle click */ }) {
        Text("Tab 1")
    }
}
SecondaryTabRow

Дополнительная панель вкладок без прокрутки.

SecondaryTabRow(selectedTabIndex = 0) {
    Tab(selected = true, onClick = { /* Handle click */ }) {
        Text("Tab 1")
    }
}
SecondaryScrollableTabRow

Дополнительная панель вкладок с прокруткой.

SecondaryScrollableTabRow(selectedTabIndex = 0) {
    Tab(selected = true, onClick = { /* Handle click */ }) {
        Text("Tab 1")
    }
}
Surface

Контейнер для отображения компонентов с поддержкой стилей, таких как elevation и фоны.

Surface(elevation = 4.dp, shape = RoundedCornerShape(8.dp)) {
    Text("Surface with elevation")
}
Chip

Элемент для отображения маленьких блоков текста или иконок.

Chip(onClick = { /* Handle click */ }) {
    Text("Chip")
}
AssistChip

Чип для дополнительных действий или помощи.

AssistChip(onClick = { /* Handle click */ }) {
    Text("Assist Chip")
}
FilterChip

Чип для фильтрации данных.

FilterChip(onClick = { /* Handle click */ }) {
    Text("Filter Chip")
}
InputChip

Чип для ввода данных.

InputChip(onClick = { /* Handle click */ }) {
    Text("Input Chip")
}
SuggestionChip

Чип для предложений.

SuggestionChip(onClick = { /* Handle click */ }) {
    Text("Suggestion Chip")
}
Badge

Отображает небольшой значок или метку.

Badge {
    Text("New")
}
Carousel

Компонент для прокрутки элементов в виде карусели.

Carousel(
    items = listOf("Item 1", "Item 2", "Item 3"),
    content = { item -> Text(item) }
)
DatePicker

Выбор даты.

DatePickerDialog(
    onDateSelected = { /* Handle date selection */ }
)
TimePicker

Выбор времени.

TimePickerDialog(
    onTimeSelected = { /* Handle time selection */ }
)
NavigationRail

Боковая панель для навигации

NavigationRail {
    NavigationRailItem(
        icon = { Icon(Icons.Filled.Home, contentDescription = null) },
        label = { Text("Home") },
        selected = false,
        onClick = { /* Handle click */ }
    )
}
SearchBar

Панель для поиска.

SearchBar(onSearch = { /* Handle search */ }) {
    Text("Search")
}
Layouts
Column

Размещает элементы вертикально.

Column {
    Text("Item 1")
    Text("Item 2")
}
Row

Размещает элементы горизонтально.

Row {
    Text("Item 1")
    Text("Item 2")
}
Box

Контейнер, который накладывает элементы друг на друга.

Box {
    Text("First item")
    Text("Second item")
}
FlowRow

Располагает элементы в строках, которые автоматически переносятся на новую строку при переполнении ширины.

FlowRow {
    repeat(10) { index ->
        Text("Item $index")
    }
}
FlowColumn

Аналог FlowRow, но с вертикальной ориентацией.

FlowColumn {
    repeat(10) { index ->
        Text("Item $index")
    }
}
ContextualFlowRow

Компонент для размещения элементов в строку с поддержкой контекстного управления.

ContextualFlowRow(
    context = /* Provide context */,
    modifier = Modifier.padding(8.dp)
) {
    items(/* List of items */) { item ->
        Text(text = item)
    }
}
ContextualFlowColumn

Компонент для размещения элементов в колонку с поддержкой контекстного управления.

ContextualFlowColumn(
    context = /* Provide context */,
    modifier = Modifier.padding(8.dp)
) {
    items(/* List of items */) { item ->
        Text(text = item)
    }
}
LazyColumn

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

LazyColumn {
    items(10) { index ->
        Text("Item $index")
    }
}
item

Добавлztn отдельный элемент в LazyColumn. Этот метод позволяет отображать один элемент без необходимости оборачивать его в коллекцию.

LazyColumn {
    item {
        Text(text = "Заголовок")
    }
    item {
        Text(text = "Первый элемент")
    }
}
items

Используется для отображения списка элементов. Он принимает коллекцию и автоматически создает элементы списка на основе этой коллекции.

LazyColumn {
    items(listOf("Первый", "Второй", "Третий")) { item ->
        Text(text = item)
    }
}
itemsIndexed

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

LazyColumn {
    itemsIndexed(listOf("Первый", "Второй", "Третий")) { index, item ->
        Text(text = "$index: $item")
    }
}
stickyHeader

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

LazyColumn {
    stickyHeader {
        Text(text = "Заголовок раздела", style = MaterialTheme.typography.h6)
    }
    items(listOf("Элемент 1", "Элемент 2", "Элемент 3")) { item ->
        Text(text = item)
    }
}
key

Используется для указания уникального ключа для каждого элемента в списке, что позволяет эффективно управлять состоянием и производительностью при обновлениях списка. Использование ключей помогает Compose отслеживать элементы и сохранять их состояние даже при изменении данных. key используется в методах items, itemsIndexed, и item, позволяя указать уникальный идентификатор для каждого элемента.

• Эффективное обновление: Когда элементы в списке обновляются, Compose может использовать ключи, чтобы определить, какие элементы были изменены, добавлены или удалены. Это позволяет избежать полной переработки всего списка и обновлять только измененные элементы.

• Сохранение состояния: Если элементы списка имеют свое состояние (например, флажки, текстовые поля и т. д.), использование уникальных ключей позволяет сохранять состояние каждого элемента, даже если порядок элементов меняется или элементы удаляются/добавляются.

data class User(val id: String, val name: String)

val users = listOf(
    User("1", "Alice"),
    User("2", "Bob"),
    User("3", "Charlie")
)

LazyColumn {
    items(users, key = { it.id }) { user ->
        Text(text = user.name)
    }
}
LazyRow

Горизонтальный список с возможностью прокрутки.

LazyRow {
    items(10) { index ->
        Text("Item $index")
    }
}
LazyVerticalGrid

Отображает элементы в виде сетки с возможностью прокрутки.

LazyVerticalGrid(cells = GridCells.Fixed(2)) {
    items(10) { index ->
        Text("Item $index")
    }
}

Добавить Header

LazyVerticalGrid(
    columns = GridCells.Fixed(count = 8),
) {
    item(
        span = { GridItemSpan(maxLineSpan) }
    ) {
        Text(
            text = "Header",
        )
    }

    items(list) { item ->
        ...            
    }
}
LazyStaggeredGrid

Прокручиваемая сетка с переменной высотой элементов.

LazyStaggeredGrid(columns = StaggeredGridCells.Fixed(2)) {
    items(100) { index ->
        Text("Item #$index")
    }
}
Scaffold

Структурный компонент для создания макетов с верхними панелями, навигацией, FAB и контентом.

Scaffold(
    topBar = { TopAppBar(title = { Text("AppBar Title") }) },
    floatingActionButton = { FloatingActionButton(onClick = { /* Действие */ }) { Text("+") } },
    content = { Text("Hello, Scaffold!") }
)
BackdropScaffold

Специальный макет, где один элемент (фоновый) может быть скрыт или отображен позади другого контента.

BackdropScaffold(
    appBar = { TopAppBar(title = { Text("Backdrop Scaffold") }) },
    backLayerContent = { Text("Back Layer") },
    frontLayerContent = { Text("Front Layer") }
)
HorizontalPager

Пейджер для горизонтальной прокрутки страниц.

HorizontalPager(count = 5) { page ->
    Text("Page $page")
}
VerticalPager

Пейджер для вертикальной прокрутки страниц (как и HorizontalPager, но с вертикальной ориентацией).

VerticalPager(count = 5) { page ->
    Text("Page $page")
}
ConstraintLayout

Chain

ConstraintLayout {
    val (icon, text) = createRefs()
    createHorizontalChain(icon, text, chainStyle = ChainStyle.Packed)

    Icon(
        modifier = Modifier
            .constrainAs(icon) {
                width = Dimension.wrapContent
                height = Dimension.wrapContent
                start.linkTo(parent.start)
                top.linkTo(parent.top)
                end.linkTo(text.start)
                bottom.linkTo(parent.bottom)
            }
    )

    Text(
        modifier = Modifier
            .constrainAs(text) {
                width = Dimension.wrapContent
                height = Dimension.wrapContent
                start.linkTo(icon.end)
                top.linkTo(parent.top)
                end.linkTo(parent.end)
                bottom.linkTo(parent.bottom)
            }
            .padding(start = 8.dp)
    )
}

Flow

FlowRow {
    Text(
        text = "Text 1",
        modifier = Modifier.padding(end = 8.dp)
    )

		Text(
        text = "Text 2",
        modifier = Modifier.padding(end = 8.dp)
    )

    Text(
        text = "Text 3"
    )       
}
Adaptive Layouts
https://developer.android.com/develop/ui/compose/layouts/adaptive
https://developer.android.com/develop/ui/compose/layouts/adaptive/adaptive-dos-and-donts
https://developer.android.com/develop/ui/compose/layouts/adaptive/support-different-screen-sizes
https://developer.android.com/develop/ui/compose/layouts/adaptive/window-size-classes
https://developer.android.com/develop/ui/compose/layouts/adaptive/support-multi-window-mode
https://developer.android.com/develop/ui/compose/layouts/adaptive/build-adaptive-navigation
https://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail
https://developer.android.com/develop/ui/compose/layouts/adaptive/build-a-supporting-pane-layout
BoxWithConstraints

Контейнер, который может изменять свою компоновку в зависимости от доступных размеров.

BoxWithConstraints { constraints ->
    if (constraints.maxWidth < 600.dp) {
        // Компоновка для узкого экрана
        Column {
            Text("Узкий экран")
            // Дополнительные элементы
        }
    } else {
        // Компоновка для широкого экрана
        Row {
            Text("Широкий экран")
            // Дополнительные элементы
        }
    }
}
NavigationSuiteScaffold

Интегрируется с NavController для навигации между экранами и поддерживает стандартные элементы интерфейса, такие как TopAppBar и BottomNavigation.

val navController = rememberNavController()
    
NavigationSuiteScaffold(
    navController = navController,
    topBar = { TopAppBar(title = { Text("MyApp") }) },
    bottomBar = {
        BottomNavigation {
            // Навигационные элементы
        }
    }
) { innerPadding ->
    NavHost(
        navController = navController,
        startDestination = "home",
        Modifier.padding(innerPadding)
    ) {
        composable("home") { HomeScreen(navController) }
        composable("details/{itemId}") { backStackEntry ->
            DetailsScreen(navController, itemId = backStackEntry.arguments?.getString("itemId"))
        }
    }
}
ListDetailPaneScaffold

Управляет макетом, разделяя экран на две основные панели: список и детальную информацию.

ListDetailPaneScaffold(
    listContent = { innerPadding ->
        LazyColumn(
            Modifier.padding(innerPadding)
        ) {
            items(items = itemsList) { item ->
                ListItem(
                    modifier = Modifier.clickable { /* Отображаем детальную информацию */ },
                    text = { Text(item.title) }
                )
            }
        }
    },
    detailContent = { innerPadding ->
        // Отображение детальной информации о выбранном элементе
        DetailView(
            modifier = Modifier.padding(innerPadding),
            item = selectedItem
        )
    }
)
SupportingPaneScaffold

Создает интерфейсы с двумя панелями — основной и вспомогательной.

SupportingPaneScaffold(
    mainContent = { innerPadding ->
        // Основной контент
        Column(
            modifier = Modifier.padding(innerPadding)
        ) {
            Text("Основной контент")
            // Дополнительные элементы
        }
    },
    supportingContent = { innerPadding ->
        // Вспомогательный контент
        Column(
            modifier = Modifier.padding(innerPadding)
        ) {
            Text("Вспомогательный контент")
            // Дополнительные элементы
        }
    }
)
NavigableListDetailPaneScaffold

Обеспечивает структуру для приложений с навигацией между списком элементов и детализированным просмотром выбранного элемента.

val navigator = rememberListDetailPaneScaffoldNavigator<Any>()

NavigableListDetailPaneScaffold(
    navigator = navigator,
    listPane = {},   // панель списка
    detailPane = {}, // Панель деталей
)
Dialogs
Popup

Всплывающее окно для отображения временного контента поверх текущего интерфейса.

Popup {
    Text("Popup content")
}
DropdownMenu

Выпадающее меню для выбора из нескольких вариантов.

DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
    DropdownMenuItem(onClick = { /* Действие */ }) {
        Text("Option 1")
    }
    DropdownMenuItem(onClick = { /* Действие */ }) {
        Text("Option 2")
    }
}
AlertDialog

Диалоговое окно для отображения сообщений и взаимодействия с пользователем.

AlertDialog(
    onDismissRequest = { /* Закрыть диалог */ },
    title = { Text("Title") },
    text = { Text("Dialog content goes here") },
    confirmButton = {
        Button(onClick = { /* Действие подтверждения */ }) {
            Text("OK")
        }
    }
)
ModalBottomSheet

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

@Composable
fun ExampleModalBottomSheet() {
    val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    val scope = rememberCoroutineScope()

    ModalBottomSheetLayout(
        sheetState = sheetState,
        sheetContent = {
            Column(modifier = Modifier.padding(16.dp)) {
                Text("Title")
                Button(onClick = { scope.launch { sheetState.hide() } }) {
                    Text("Close Bottom Sheet")
                }
            }
        }
    ) {
        Button(onClick = { scope.launch { sheetState.show() } }) {
            Text("Open Bottom Sheet")
        }
    }
}
ModalBottomSheetLayout

Открывает листовое окно снизу для отображения контента.

ModalBottomSheetLayout(
    sheetContent = { Text("Bottom Sheet Content") },
    content = { Text("Main Content") }
)
TooltipBox

Контейнер для отображения подсказок.

TooltipBox(tooltip = { Text("Tooltip") }) {
    Text("Hover me")
}
BasicTooltipBox

Простой контейнер для подсказок.

BasicTooltipBox(
    tooltip = { Text("Tooltip") }
) {
    Text("Hover me")
}
AnimatedVisibility

Компонент для плавного отображения или скрытия контента.

var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
    visible = visible
) {
    Text("Animated Content")
}
Crossfade

Компонент для анимации плавной смены контента.

Crossfade(
    targetState = selectedScreen
) { screen ->
    when (screen) {
        Screen.Home -> HomeScreen()
        Screen.Profile -> ProfileScreen()
    }
}
Placeholder

Контейнер для отображения placeholder-заполнителя (например, для состояния загрузки).

Placeholder(
    visible = true, 
    modifier = Modifier.size(100.dp)
)
AndroidView

Позволяет встроить классический Android View в Compose.

AndroidView(
    factory = { context -> 
        TextView(context).apply { text = "Hello, View!" } 
    }
)
Snackbar
21.08.2024https://youtu.be/KFazs62lIkE

Всплывающее уведомление в нижней части экрана.

class MyViewModel: ViewModel() {

    var isSnackbarShowed: Boolean by mutableStateOf(false)

		fun showSnackbarMessage() {
			  isSnackbarShowed = true
		}

    fun hideSnackbarMessage() {
        isSnackbarShowed = false
    }
}

@Composable
fun Composable(
		viewModel: MyViewModel = hiltViewModel()
) {
		val scope = rememberCoroutineScope()
    val snackbarHostState = remember { SnackbarHostState() }
		val isSnackbarShowed = viewModel.isSnackbarShowed

		val onShowSnackbar: (String) -> Unit = { message ->
        scope.launch {
            snackbarHostState.currentSnackbarData?.dismiss()
						val snackbarResult = snackbarHostState.showSnackbar(
                message = message,
                duration = SnackbarDuration.Short
            )
            if (snackbarResult == SnackbarResult.Dismissed) {
                viewModel.hideSnackbarMessage()
            }
        }
    }

		if (isSnackbarShowed) {
        onShowSnackbar(stringResource(R.string.message))
    }
}

Scaffold(
		snackbarHost = {
        SnackbarHost(
            hostState = snackbarHostState
        )
    }
) { innerPadding -> 
    
}
Layout

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

• Каждый Layout проходит два этапа: измерение и компоновка.

• Измерение: На этом этапе вы определяете размеры дочерних компонентов, используя метод measure. Вы можете использовать переданные ограничения (constraints), чтобы измерить каждый дочерний элемент и получить его размеры.

• Компоновка: После измерения происходит этап компоновки, где вы размещаете дочерние элементы на экране. Этот процесс управляется методом layout, где вы указываете координаты размещения для каждого элемента.

// Размещает дочерние элементы в строку с заданным интервалом.

@Composable
fun CustomRowLayout(
    modifier: Modifier = Modifier,
    spacing: Int = 8,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier
    ) { measurables, constraints ->
        val placeables = measurables.map { measurable ->
            measurable.measure(constraints) // Измеряет размеры дочерних элементов и вычисляет общий размер для размещения.
        }

        val totalWidth = placeables.sumOf { it.width } + (spacing * (placeables.size - 1))
        val maxHeight = placeables.maxOfOrNull { it.height } ?: 0

        layout(totalWidth, maxHeight) { // Размещает элементы в строке, учитывая заданный интервал между ними.
            var xPosition = 0

            placeables.forEach { placeable ->
                placeable.place(xPosition, 0)
                xPosition += placeable.width + spacing
            }
        }
    }
}

@Composable
fun MyScreen() {
    CustomRowLayout(spacing = 16) {
        Box(modifier = Modifier.size(50.dp).background(Color.Red))
        Box(modifier = Modifier.size(50.dp).background(Color.Green))
        Box(modifier = Modifier.size(50.dp).background(Color.Blue))
    }
}

Constraints

• В Compose вы работаете с Constraints, которые описывают размеры и ограничения, в которых дочерние элементы могут быть размещены.

Constraints могут иметь минимальные и максимальные значения для ширины и высоты, которые определяют допустимые размеры элементов.

Placeables

Placeable — это результат измерения дочернего элемента. Он содержит информацию о размерах и может быть размещен в определенных координатах на экране.

Вопросы на собесе (8)
  • LazyColumn (4)
    1. Для чего используется key в LazyColumn?

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

    1. Что будет если не задавать ключ в LazyColumn?

      Без ключа в LazyColumn элементы могут перерисовываться и терять свое состояние при прокрутке, так как Compose не сможет эффективно отслеживать уникальные элементы.

    1. LazyColumn лагает при прокрутке. Что будем делать?

      Оптимизируйте элементы списка, минимизируя их размер и сложность.

      Используйте remember для кэширования состояния элементов.

      Избегайте ненужных повторных пересчетов, применяя key для элементов списка.

      Проверьте, нет ли тяжелых операций в item или itemContent.

    1. Разница между LazyColumn и Column?

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

  • FlowRow (1)
    1. Для чего используется FlowRow?

      Располагает элементы в строках, которые автоматически переносятся на новую строку при переполнении ширины.

  • Другие (3)
    1. Какие основные Layout есть в Compose?

      Box Row Column

    1. Как сделать отступ у Box?

      Используйте модификатор padding.

    1. Как создать собственную View в Compose используя Layout?

      Использовать компонент Layout, который позволяет определить, как измерять и размещать дочерние элементы. Внутри функции Layout следует реализовать логику измерения (метод measure) и размещения (метод layout) дочерних компонентов, учитывая их размеры и необходимые отступы. Используя кастомный Layout, вы можете контролировать, как будут отображаться и взаимодействовать ваши элементы, обеспечивая необходимую гибкость в дизайне интерфейса.


Page comments
  • Михаил Белый
    Все виджеты взяты из темы Material3. Старый дизайн мне не нравится, хули его поддерживать