Peter
06/19/2024, 7:58 AMPeter
06/19/2024, 7:58 AMimport androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
import com.my.app.R
@Composable
fun CollapsableHeader(
scrollPosition: Int,
topContent: @Composable (() -> Unit),
titleContent: @Composable (() -> Unit),
backgroundContent: @Composable () -> Unit,
modifier: Modifier = Modifier,
collapsableContent: (@Composable () -> Unit)? = {},
bottomContent: (@Composable () -> Unit)? = {},
) {
Box(modifier = modifier) {
Background(backgroundContent = backgroundContent, modifier = Modifier.matchParentSize())
Foreground(
scrollPosition = scrollPosition,
topContent = topContent,
titleContent = titleContent,
collapsableContent = collapsableContent,
bottomContent = bottomContent,
)
}
}
@Composable
private fun Background(backgroundContent: @Composable () -> Unit, modifier: Modifier = Modifier) {
Box(modifier = modifier) {
backgroundContent()
}
}
@Composable
private fun Foreground(
scrollPosition: Int,
topContent: @Composable (() -> Unit),
titleContent: @Composable (() -> Unit),
collapsableContent: (@Composable () -> Unit)? = null,
bottomContent: (@Composable () -> Unit)? = null,
) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimary) {
Layout(
content = {
Box(modifier = Modifier.statusBarsPadding()) {
topContent()
}
ProvideTextStyle(MaterialTheme.typography.titleLarge) {
titleContent()
}
Box {
collapsableContent?.invoke()
}
Box {
bottomContent?.invoke()
}
},
) { measurables, constraints ->
val top = measurables[0].measure(constraints)
val title = measurables[1].measure(constraints)
val collapsable = measurables[2].measure(constraints)
val bottom = measurables[3].measure(constraints)
val screenWidth = constraints.maxWidth
val nodesHeight = top.height + title.height + bottom.height
val paddingHeight = (screenWidth - nodesHeight - collapsable.height).coerceAtLeast(0)
val titleTopPadding = (paddingHeight - scrollPosition).coerceAtLeast(0)
val collapsableAlpha = (1f - scrollPosition.toFloat() / paddingHeight).coerceIn(0f, 1f)
val collapsableHeight = if (titleTopPadding > 0) {
collapsable.height
} else {
(collapsable.height - scrollPosition + paddingHeight).coerceAtLeast(0)
}
val height = nodesHeight + titleTopPadding + collapsableHeight
layout(width = screenWidth, height = height) {
var currentY = 0
top.place(x = 0, y = 0)
currentY += titleTopPadding + top.height
title.place(x = 0, y = currentY)
currentY += title.height
if (collapsableHeight > 0) {
collapsable.placeWithLayer(x = 0, y = currentY) { alpha = collapsableAlpha }
currentY += collapsableHeight
}
bottom.place(x = 0, y = currentY)
}
}
}
}
@Composable
@Preview
private fun PreviewBehavior() {
Surface {
val scroll = rememberScrollState()
Column {
CollapsableHeader(
scrollPosition = scroll.value,
topContent = { BackArrow(onBackClick = { }) },
titleContent = { Text(loremIpsum(7)) },
collapsableContent = { Text(loremIpsum(7)) },
bottomContent = { Text(loremIpsum(7)) },
backgroundContent = {
Image(
painter = painterResource(id = R.drawable.bg_something),
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
)
},
)
Text(
text = loremIpsum(100),
modifier = Modifier
.verticalScroll(scroll)
.height(2000.dp)
.fillMaxWidth()
.background(Color.Red),
)
}
}
}
fun loremIpsum(words: Int): String {
return LoremIpsum(words).values.first()
}
Dmitry Strekha
06/19/2024, 9:55 PM