Utilities

Vertical Parallax Effect

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.dp
import com.composables.uripainter.rememberUriPainter

@Composable
fun VerticalParallaxEffect() {
    data class ArticleSummary(val title: String, val coverUrl: String)

    val articles = listOf(
        ArticleSummary(
            title = "The Future of Sustainable Architecture",
            coverUrl = "https://images.unsplash.com/photo-1448630360428-65456885c650?q=80&w=1920"
        ),
        ArticleSummary(
            title = "The Art of Minimalist Design",
            coverUrl = "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?q=80&w=1920"
        ),
        ArticleSummary(
            title = "Coffee Culture Around the World",
            coverUrl = "https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?q=80&w=1920"
        ),
        ArticleSummary(
            title = "The Science of Sleep and Productivity",
            coverUrl = "https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?q=80&w=1920"
        ),
        ArticleSummary(
            title = "Ocean Conservation: Protecting Marine Life",
            coverUrl = "https://images.unsplash.com/photo-1544551763-46a013bb70d5?q=80&w=1920"
        ),
        ArticleSummary(
            title = "The Rise of Remote Work Culture",
            coverUrl = "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?q=80&w=1920"
        ),
        ArticleSummary(
            title = "Ancient Forests: Nature's Timeless Beauty",
            coverUrl = "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=1920"
        ),
        ArticleSummary(
            title = "Urban Gardening: Green Cities Initiative",
            coverUrl = "https://images.unsplash.com/photo-1416879595882-3373a0480b5b?q=80&w=1920"
        ),
        ArticleSummary(
            title = "The Art of Japanese Tea Ceremony",
            coverUrl = "https://images.unsplash.com/photo-1544787219-7f47ccb76574?q=80&w=1920"
        ),
        ArticleSummary(
            title = "Northern Lights: Arctic Wonder",
            coverUrl = "https://images.unsplash.com/photo-1531366936337-7c912a4589a7?q=80&w=1920"
        ),
        ArticleSummary(
            title = "Desert Landscapes: Beauty in Aridity",
            coverUrl = "https://images.unsplash.com/photo-1509316785289-025f5b846b35?q=80&w=1920"
        ),
    )

    fun Modifier.verticalParallax(listState: LazyListState, index: Int, rate: Float = 2f) = this.then(
        Modifier.layout { measurable, constraints ->
            val placeable = measurable.measure(constraints)
            val itemOffset = listState.layoutInfo.visibleItemsInfo.find { it.index == index }?.offset ?: 0
            val offsetY = itemOffset / rate
            layout(placeable.width, placeable.height) {
                placeable.place(0, offsetY.toInt())
            }
        }
    )

    val state = rememberLazyListState()
    LazyColumn(
        state = state,
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(24.dp),
        contentPadding = PaddingValues(24.dp),
    ) {
        articles.forEachIndexed { i, article ->
            item {
                Card(Modifier.height(280.dp)) {
                    Box(Modifier.fillMaxSize()) {
                        Image(
                            painter = rememberUriPainter(article.coverUrl),
                            contentDescription = article.title,
                            contentScale = ContentScale.Crop,
                            modifier = Modifier
                                .fillMaxWidth()
                                .wrapContentHeight(unbounded = true)
                                .verticalParallax(state, rate = 5f, index = i)
                        )

                        Box(
                            Modifier
                                .align(Alignment.BottomStart)
                                .fillMaxWidth()
                                .background(
                                    Brush.verticalGradient(
                                        colors = listOf(
                                            Color.Transparent,
                                            Color.Black.copy(alpha = 0.2f),
                                            Color.Black.copy(alpha = 0.4f)
                                        )
                                    )
                                )
                                .padding(16.dp)
                        ) {
                            Text(article.title, color = Color.White)
                        }
                    }
                }
            }
        }
    }
}

Explore other Jetpack Compose Blocks