Как реализовать Collapsing Toolbar в Jetpack Compose?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация Collapsing Toolbar в Jetpack Compose
В Jetpack Compose для создания эффекта collapsing toolbar (складывающегося заголовка), аналогичного тому, что было в Android View System (CollapsingToolbarLayout), используется комбинация нескольких современных компонентов, таких как LazyColumn, Modifier.pinnedHeader(), Box, и эффекты анимации. В отличие от традиционного подхода с XML, Compose предлагает более декларативный и гибкий способ.
Основные концепции и компоненты
Ключевая идея заключается в том, что мы создаём scrollable list (LazyColumn или LazyList), в который интегрируем наш toolbar, и управляем его видимостью, размером и позицией в зависимости от состояния прокрутки. Основные инструменты для этого:
LazyColumn– вертикальный список, поддерживающий прокрутку и возможность «закрепления» заголовков.Modifier.pinnedHeader()– модификатор для элементовLazyColumn, который позволяет «прикрепить» элемент (например, наш toolbar) к верхней части списка при прокрутке.- Анимации и состояния – использование
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)
)
}
}
}
}
Алгоритм и логика работы
- Определение состояния прокрутки: Мы используем
rememberLazyListState()для отслеживания позиции скролла вLazyColumn. - Адаптация высоты toolbar: В примере выше высота toolbar динамически вычисляется на основе
firstVisibleItemScrollOffset. Когда пользователь начинает прокрутку от самого верхнего элемента (расширенного заголовка),scrollOffsetувеличивается, и мы уменьшаем высоту toolbar от максимального значения (200.dp) до минимального (56.dp). Для плавности применяетсяanimateFloatAsState. - Закрепление заголовка: Используя
pinnedHeaderвнутриLazyColumn, мы создаём элемент, который остаётся видимым в верхней части экрана после того, как исходный расширенный заголовок «скроется» за пределы видимости при прокрутке. Это имитирует поведение классического collapsing toolbar. - Дополнительные эффекты: Можно расширить логику, добавив анимацию цвета текста, изменения размера текста (
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 для закрепления элементов и анимаций.