Created
August 7, 2025 18:29
-
-
Save ardakazanci/597337b56f03516bbe744412a7f279cf to your computer and use it in GitHub Desktop.
Revisions
-
ardakazanci created this gist
Aug 7, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,97 @@ @Composable fun Modifier.parallaxHeader( listState: LazyListState, headerHeightDp: Dp, maxStretchFactor: Float = 3.0f, pullMultiplier: Float = 1.5f, onHeightChanged: (Dp) -> Unit ): Modifier { val density = LocalDensity.current val coroutineScope = rememberCoroutineScope() val headerHeightPx = with(density) { headerHeightDp.toPx() } val currentHeaderHeightPx = remember { Animatable(headerHeightPx) } onHeightChanged(with(density) { currentHeaderHeightPx.value.toDp() }) val nestedScrollConnection = remember { object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { val delta = available.y if (delta < 0 && listState.firstVisibleItemIndex == 0 && currentHeaderHeightPx.value > headerHeightPx) { val newHeight = (currentHeaderHeightPx.value + delta).coerceAtLeast(headerHeightPx) val consumedDelta = newHeight - currentHeaderHeightPx.value coroutineScope.launch { currentHeaderHeightPx.snapTo(newHeight) } return Offset(0f, consumedDelta) } if (delta > 0 && listState.firstVisibleItemIndex == 0) { val newHeight = (currentHeaderHeightPx.value + delta * pullMultiplier) .coerceAtMost(headerHeightPx * maxStretchFactor) coroutineScope.launch { currentHeaderHeightPx.snapTo(newHeight) } return Offset(0f, delta) } return Offset.Zero } override suspend fun onPreFling(available: Velocity): Velocity { if (currentHeaderHeightPx.value > headerHeightPx) { currentHeaderHeightPx.animateTo( targetValue = headerHeightPx, animationSpec = spring( stiffness = Spring.StiffnessMediumLow, dampingRatio = Spring.DampingRatioMediumBouncy ) ) } return super.onPreFling(available) } } } return this.nestedScroll(nestedScrollConnection) } @Composable fun ParallaxScreen() { val listState = rememberLazyListState() val baseHeaderHeight = 210.dp var headerHeight by remember { mutableStateOf(baseHeaderHeight) } Box( modifier = Modifier .fillMaxSize() .parallaxHeader( listState = listState, headerHeightDp = baseHeaderHeight, onHeightChanged = { headerHeight = it } ) ) { Image( painter = painterResource(R.drawable.poke), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier .height(headerHeight) .fillMaxWidth() .clipToBounds() ) LazyColumn( state = listState, contentPadding = PaddingValues(top = headerHeight) ) { items(50) { index -> Text( text = "Item #$index", modifier = Modifier .fillMaxWidth() .height(80.dp) .background(Color.White) .padding(16.dp) ) } } } }