← Назад к вопросам

Как реализовать Collapsing Toolbar в Jetpack Compose?

1.0 Junior🔥 141 комментариев
#Android компоненты#UI и вёрстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация Collapsing Toolbar в Jetpack Compose

В Jetpack Compose для создания эффекта collapsing toolbar (складывающегося заголовка), аналогичного тому, что было в Android View System (CollapsingToolbarLayout), используется комбинация нескольких современных компонентов, таких как LazyColumn, Modifier.pinnedHeader(), Box, и эффекты анимации. В отличие от традиционного подхода с XML, Compose предлагает более декларативный и гибкий способ.

Основные концепции и компоненты

Ключевая идея заключается в том, что мы создаём scrollable list (LazyColumn или LazyList), в который интегрируем наш toolbar, и управляем его видимостью, размером и позицией в зависимости от состояния прокрутки. Основные инструменты для этого:

  1. LazyColumn – вертикальный список, поддерживающий прокрутку и возможность «закрепления» заголовков.
  2. Modifier.pinnedHeader() – модификатор для элементов LazyColumn, который позволяет «прикрепить» элемент (например, наш toolbar) к верхней части списка при прокрутке.
  3. Анимации и состояния – использование animateFloatAsState, animateColorAsState и других для плавного изменения свойств toolbar (высоты, цвета текста, прозрачности) в ответ на скролл.

Практическая реализация

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

@Composable
fun CollapsingToolbarScreen() {
    val scrollState = rememberLazyListState()
    val lazyListScope = rememberLazyListScope()

    // Пример анимации высоты toolbar на основе позиции прокрутки
    val toolbarHeight by animateFloatAsState(
        targetValue = if (scrollState.firstVisibleItemIndex > 0) {
            56f // Минимальная высота (например, для закрепленного состояния)
        } else {
            // Вычисляем высоту на основе первого видимого элемента
            val offset = scrollState.firstVisibleItemScrollOffset
            // Максимальная высота - минимальная, уменьшаем пропорционально offset
            (200f - offset.coerceAtMost(200)).toFloat()
        }
    )

    Box(modifier = Modifier.fillMaxSize()) {
        LazyColumn(state = scrollState) {
            // Элемент 0: Расширенный заголовок (Toolbar в развернутом состоянии)
            item {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(200.dp)
                        .background(Color.Blue),
                    contentAlignment = Alignment.Center
                ) {
                    Text(
                        text = "Expanded Title",
                        fontSize = 24.sp,
                        color = Color.White
                    )
                }
            }
            
            // Закрепленный заголовок (Collapsing Toolbar)
            // Этот элемент будет "прикреплен" сверху при прокрутке
            pinnedHeader {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(toolbarHeight.dp)
                        .background(Color.Blue.copy(alpha = 0.9f)),
                    contentAlignment = Alignment.CenterStart
                ) {
                    Text(
                        text = "Collapsed Title",
                        fontSize = 18.sp,
                        color = Color.White,
                        modifier = Modifier.padding(horizontal = 16.dp)
                    )
                }
            }
            
            // Контент списка
            items(100) { index ->
                Text(
                    text = "Item $index",
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                )
            }
        }
    }
}

Алгоритм и логика работы

  1. Определение состояния прокрутки: Мы используем rememberLazyListState() для отслеживания позиции скролла в LazyColumn.
  2. Адаптация высоты toolbar: В примере выше высота toolbar динамически вычисляется на основе firstVisibleItemScrollOffset. Когда пользователь начинает прокрутку от самого верхнего элемента (расширенного заголовка), scrollOffset увеличивается, и мы уменьшаем высоту toolbar от максимального значения (200.dp) до минимального (56.dp). Для плавности применяется animateFloatAsState.
  3. Закрепление заголовка: Используя pinnedHeader внутри LazyColumn, мы создаём элемент, который остаётся видимым в верхней части экрана после того, как исходный расширенный заголовок «скроется» за пределы видимости при прокрутке. Это имитирует поведение классического collapsing toolbar.
  4. Дополнительные эффекты: Можно расширить логику, добавив анимацию цвета текста, изменения размера текста (animateDpAsState), или трансформацию других элементов (например, изображения) в toolbar, используя те же принципы реакции на scrollState.

Преимущества подхода в Compose

  • Декларативность: Логика описывается как реакция на состояние (scrollState), а не через прямые вызовы методов, что делает код более читаемым и устойчивым.
  • Гибкость: Вы можете создать практически любой вид collapsing эффекта — не только для заголовков, но и для любых составных элементов интерфейса.
  • Интеграция с другими функциями Compose: Легко сочетается с Modifier.graphicsLayer для сложных трансформаций, Modifier.offset для управления позицией, или Crossfade для замены содержимого toolbar при скролле.

Ключевые моменты для сложных реализаций

Для более сложных сценариев (например, когда collapsing toolbar содержит изображение, которое должно масштабироваться и менять прозрачность):

val imageScale by animateFloatAsState(
    targetValue = if (scrollState.firstVisibleItemIndex > 0) 1f else {
        1f + (scrollState.firstVisibleItemScrollOffset / 500f).coerceAtMost(0.5f)
    }
)

Box(
    modifier = Modifier
        .graphicsLayer { scaleX = imageScale; scaleY = imageScale }
        .background(Color.Gray)
) {
    Image(/* ... */)
}

Таким образом, реализация collapsing toolbar в Jetpack Compose сводится к управлению состоянием прокрутки (LazyListState) и анимированным изменениям свойств элементов на основе этого состояния, с использованием встроенных механизмов Compose для закрепления элементов и анимаций.

Как реализовать Collapsing Toolbar в Jetpack Compose? | PrepBro