Home > Enterprise >  Offset a wide image for horizontal parallax effect in Android Compose
Offset a wide image for horizontal parallax effect in Android Compose

Time:06-07

I am trying to create a parallax effect with a wide image lets say: Demo in the middle of scrolling

As you can see while going through the list white space appears on the right instead of the remaining image.

How do i do this properly?

CodePudding user response:

Image is too smart and doesn't draw anything beyond the bounds. translationX doesn't change the bound but only moves the view.

Here's how you can draw it manually:

val painter = painterResource(id = R.drawable.my_image_1)
Canvas(
    modifier = Modifier
        .fillMaxWidth()
        .height(BG_IMAGE_HEIGHT)
) {
    translate(
        left = -parallaxOffset,
    ) {
        with(painter) {
            draw(Size(width = painter.intrinsicSize.aspectRatio * size.height, height = size.height))
        }
    }
}

I don't see your code that calculates parallaxOffset, but just in case, I suggest you watch enter image description here

CodePudding user response:

I'm leaving my solution here...

@Composable
private fun ListBg(
    firstVisibleIndex: Int,
    totalVisibleItems: Int,
    firstVisibleItemOffset: Int,
    itemsCount: Int,
    maxWidth: Dp
) {
    val density = LocalDensity.current
    val firstItemOffsetDp =
        with(density) { firstVisibleItemOffset.toDp() } / itemsCount
    val hasNoScroll = itemsCount <= totalVisibleItems
    val totalWidth = if (hasNoScroll) maxWidth else maxWidth * 2
    val scrollableBgWidth = if (hasNoScroll) maxWidth else totalWidth - maxWidth
    val scrollStep = scrollableBgWidth / itemsCount
    val xOffset = if (hasNoScroll) 0.dp else -(scrollStep * firstVisibleIndex) - firstItemOffsetDp
    Box(
        Modifier
            .wrapContentWidth(unbounded = true, align = Alignment.Start)
            .offset(x = xOffset)
    ) {
        Image(
            painter = rememberAsyncImagePainter(
                model = "https://placekitten.com/2000/400",
                contentScale = ContentScale.FillWidth,
            ),
            contentDescription = null,
            alignment = Alignment.TopCenter,
            modifier = Modifier
                .height(232.dp)
                .width(totalWidth)
        )
    }
}

@Composable
fun ListWithParallaxImageScreen() {
    val lazyListState = rememberLazyListState()
    val firstVisibleIndex by remember {
        derivedStateOf {
            lazyListState.firstVisibleItemIndex
        }
    }
    val totalVisibleItems by remember {
        derivedStateOf {
            lazyListState.layoutInfo.visibleItemsInfo.size
        }
    }
    val firstVisibleItemOffset by remember {
        derivedStateOf {
            lazyListState.firstVisibleItemScrollOffset
        }
    }
    val itemsCount = 10
    BoxWithConstraints(Modifier.fillMaxSize()) {
        ListBg(firstVisibleIndex, totalVisibleItems, firstVisibleItemOffset, itemsCount, maxWidth)
        LazyRow(state = lazyListState, modifier = Modifier.fillMaxSize()) {
            items(itemsCount) {
                Card(
                    backgroundColor = Color.LightGray.copy(alpha = .5f),
                    modifier = Modifier
                        .padding(16.dp)
                        .width(300.dp)
                        .height(200.dp)
                ) {
                    Text(
                        text = "Item $it",
                        Modifier
                            .padding(horizontal = 16.dp, vertical = 6.dp)
                    )
                }
            }
        }
    }
}

Here is the result:

enter image description here

  • Related