Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jetpack Compose: Animate long horizontal image off-screen

I have a Compose screen that needs to contain a long horizontal PNG image that initially shows as below, most of it showing off-screen to the right. I am needing to slowly animate it left, then right again (then repeat):

enter image description here

I was able to do this in old XML code and kotlin, but with Compose, I'm a bit lost on where to begin. As a first step (before even getting to the animating) - I attempted to use an Image for starters to show it off the screen by manually setting it a long size, but it squishes it within the view. I also tried this approach:

Canvas(
    modifier = Modifier
        .fillMaxSize()
) {
    with(painter) {
        draw(
            size = Size(1000f, 300f),
        )
    }
}

But it still squishes the image within the view. I also tried

val imageBitmapSrc = ImageBitmap.imageResource(
    LocalContext.current.resources,
    R.drawable.landing_images2
)
Canvas(
    modifier = Modifier
        .fillMaxSize()
) {
    drawImage(
        image = imageBitmapSrc,
        dstSize = IntSize(1000, 300)
    )
}

Same result there as well, although at least with the last approach it lets me offset it off the screen (but the size is way off, should be very long and showing only part of the image without squishing it). Thoughts on what I'm doing wrong at least for this first step?

like image 358
svguerin3 Avatar asked Oct 27 '25 13:10

svguerin3


1 Answers

As a first step (before even getting to the animating) - I attempted to use an Image for starters to show it off the screen by manually setting it a long size, but it squishes it within the view.

You are on the right track, you just need to set appropriate content scale to the Image:

Image(
    modifier = Modifier
        .fillMaxWidth()
        .height(300.dp),
    painter = painterResource(R.drawable.landing_images2),
    contentDescription = null,
    contentScale = ContentScale.FillHeight,
)

This will create image with 300dp height and full width and it sets contentScale to ContentScale.FillHeight, which means that it will scale the image such that its height will be 300dp, preserving the aspect ratio of the original image. Default contentScale is ContentScale.Fit, which scales the image such that both dimensions fit the composable size - you don't want that and I guess this was your main problem.

The animation can then look like this:

val horizontalBias by rememberInfiniteTransition()
    .animateFloat(
        initialValue = -1F,
        targetValue = 1F,
        animationSpec = infiniteRepeatable(
            animation = tween(3000),
            repeatMode = RepeatMode.Reverse,
        ),
    )

Image(
    modifier = Modifier
        .fillMaxWidth()
        .height(300.dp),
    painter = painterResource(R.drawable.landing_images2),
    contentDescription = null,
    contentScale = ContentScale.FillHeight,
    alignment = BiasAlignment(horizontalBias, 0F),
)

This makes use of BiasAlignment, where BiasAlignment(-1F, 0F) aligns to center start and BiasAlignment(1F, 0F) aligns to center end. Simply animating horizontalBias from -1 to 1 infinitely then does the animation you want.

like image 61
Jan Bína Avatar answered Oct 30 '25 05:10

Jan Bína