https://gist.github.com/micHar/3926308e9467bac806f15f615b8a9c72
Each items has a different background (mainly first, last, remaining items share the same). It's the same than with recyclerview but here in compose the use of modifiers makes it really simple.
Contains simple solutions for things like snapping, infinite list, playing with input gesture.
https://gist.github.com/andkulikov/c5cd7cd2b5b5bc2e74a9dc7255d2ebdf
A tutorial showcase -> https://github.com/andkulikov/compose-photoapp/commit/0ce26695e7238185fb202f57ef323b50d4e5b96d
Albert Chang :
@Composable
fun Screen() {
var collapsibleHeight by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(0f) }
val connection = remember {
object : NestedScrollConnection {
private fun consume(available: Offset): Offset {
val newOffset = (offset + available.y).coerceIn(-collapsibleHeight, 0f)
return Offset(0f, newOffset - offset).also { offset = newOffset }
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset =
if (available.y >= 0) Offset.Zero else consume(available)
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset = consume(available)
}
}
CustomColumn(
modifier = Modifier.nestedScroll(connection),
offset = offset.roundToInt(),
onCollapsibleHeightChanged = { collapsibleHeight = it.toFloat() }
) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
.padding(16.dp)
) {}
Surface(
shape = RoundedCornerShape(16.dp),
elevation = 4.dp
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(100) { i ->
@OptIn(ExperimentalMaterialApi::class)
ListItem {
Text(text = i.toString())
}
}
}
}
}
}
@Composable
private fun CustomColumn(
modifier: Modifier = Modifier,
offset: Int = 0,
onCollapsibleHeightChanged: (Int) -> Unit = {},
content: @Composable () -> Unit
) {
Layout(content, modifier) { measurables, constraints ->
var collapsibleHeight = 0
val placeables = measurables.mapIndexed { i, measurable ->
val childConstraints = Constraints(
maxWidth = constraints.maxWidth,
maxHeight = if (i == measurables.lastIndex) {
constraints.maxHeight
} else {
constraints.maxHeight - collapsibleHeight
}
)
measurable.measure(childConstraints).also {
if (i < measurables.lastIndex) collapsibleHeight += it.height
}
}
onCollapsibleHeightChanged(collapsibleHeight)
collapsibleHeight = 0
layout(constraints.maxWidth, constraints.maxHeight) {
placeables.forEachIndexed { i, placeable ->
if (i == placeables.lastIndex) {
placeable.place(0, collapsibleHeight + offset)
} else {
placeable.place(0, collapsibleHeight)
collapsibleHeight += placeable.height
}
}
}
}
}
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1646998371832069
It uses the view mecanism + require an id declared in res but it's the cleanest way (+ handle ref counting)
private val View.keepScreenOnState: KeepScreenOnState
get() = getTag(R.id.keep_screen_on_state) as? KeepScreenOnState
?: KeepScreenOnState(this).also { setTag(R.id.keep_screen_on_state, it) }
private class KeepScreenOnState(private val view: View) {
private var refCount = 0
set(value) {
val newValue = value.coerceAtLeast(0)
field = newValue
view.keepScreenOn = newValue > 0
}
fun request() {
refCount++
}
fun release() {
refCount--
}
}
@Composable
fun KeepScreenOnRequest() {
val view = LocalView.current
DisposableEffect(view) {
val ksoState = view.keepScreenOnState
ksoState.request()
onDispose {
ksoState.release()
}
}
}