Skip to content

Instantly share code, notes, and snippets.

@zskamljic
Created May 30, 2021 08:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zskamljic/b32232fb1c3358e3669cd08e8e2c9b01 to your computer and use it in GitHub Desktop.
Save zskamljic/b32232fb1c3358e3669cd08e8e2c9b01 to your computer and use it in GitHub Desktop.
package com.playground.ui.scroll
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.playground.ui.theme.CustomTheme
import kotlinx.coroutines.launch
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.cos
@Composable
fun CurvedScroll(
itemCount: Int,
item: @Composable (Int) -> Unit
) {
val scrollState = rememberScrollState()
var size by remember { mutableStateOf(IntSize.Zero) }
val scope = rememberCoroutineScope()
val indices = remember { IntArray(itemCount) { 0 } }
val flingBehavior = object : FlingBehavior {
override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
val value = scrollState.value
indices.minByOrNull { abs(it - value) }?.let {
scope.launch {
scrollState.animateScrollTo(it)
}
}
return initialVelocity
}
}
Box(modifier = Modifier.onSizeChanged { size = it }) {
Layout(
modifier = Modifier.verticalScroll(scrollState, flingBehavior = flingBehavior),
content = { repeat(itemCount) { item(it) } }
) { measurables, constraints ->
val itemSpacing = 16.dp.roundToPx()
var contentHeight = (itemCount - 1) * itemSpacing
val placeables = measurables.mapIndexed { index, measurable ->
val placeable = measurable.measure(constraints)
contentHeight += if (index == 0 || index == measurables.lastIndex) {
placeable.height / 2
} else {
placeable.height
}
placeable
}
layout(constraints.maxWidth, size.height + contentHeight) {
val startOffset = size.height / 2 - placeables[0].height / 2
var yPosition = startOffset
val scrollPercent = scrollState.value.toFloat() / scrollState.maxValue
placeables.forEachIndexed { index, placeable ->
val elementRatio = index.toFloat() / placeables.lastIndex
val interpolatedValue = cos((scrollPercent - elementRatio) * PI)
val indent = interpolatedValue * size.width / 2
placeable.placeRelativeWithLayer(x = indent.toInt(), y = yPosition) {
alpha = interpolatedValue.toFloat()
}
indices[index] = yPosition - startOffset
yPosition += placeable.height + itemSpacing
}
}
}
}
}
@Preview
@Composable
fun CurvedScrollPreview() {
val items = listOf(
"One",
"Two",
"Three",
"Four",
"Five",
"Six"
)
CustomTheme {
Box(
modifier = Modifier
.background(Color.Gray)
.height(300.dp),
contentAlignment = Alignment.Center,
) {
CurvedScroll(items.count()) {
Text(text = items[it], style = MaterialTheme.typography.h4)
}
Divider(color = Color.Red)
Divider(
Modifier
.fillMaxHeight()
.width(1.dp), color = Color.Red
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment