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)
                        }
                    }
                }
            }
        }
    }
}

Be notified when we ship more UI Blocks

Subscribe to get updates on when we ship new components.

Horizontal Pager Indicator

Be notified when we ship more UI Blocks

Subscribe to get updates on when we ship new components.

Explore other Jetpack Compose Blocks