Thread
#compose
    Colton Idle

    Colton Idle

    1 year ago
    How can I place an image on the screen with a concrete height (140.dp in this case) which then affects the width of the image, but I want to offset the image alllllll the way on the left. Right now I hardcoded it to -200.dp and it works great on my pixel5, but running it on the emulator, it doesn't get pushed off the screen entirely. Do I have to try to measure the width somehow after height is applied and set offset that way?
    Box() {
        Image(
            modifier = Modifier.height(140.dp).offset(x = (-200).dp),
            painter = painterResource(id = com.myapp.myresources.R.drawable.catdog),
            contentDescription = null)
    }
    m

    ms

    1 year ago
    set
    contentAlignment
    to Start for Box, that may help
    Colton Idle

    Colton Idle

    1 year ago
    For reference... this is what I'm trying to achieve as the start position of my image (circle)
    Albert Chang

    Albert Chang

    1 year ago
    I think using a custom layout or a layout modifier would be the simplest way. You just measure the image and do
    placeable.place(-placeable.width, 0)
    .
    Colton Idle

    Colton Idle

    1 year ago
    Gotcha. I was looking towards using offset because I'm going to animate this offset from off of the screen to on screen, bit by bit. In that case would you say a custom layout would still be the way to go?
    Basically I'm looking to animate it from off the screen... to on & off the screen, to middle of the screen every time the user presses a continue button at the bottom of the screen.
    Albert Chang

    Albert Chang

    1 year ago
    You can animate the offset the same way.
    val offset = remember { Animatable(0) }
    // In layout block
    placeable.place(offset.value - placeable.width, 0)
    Colton Idle

    Colton Idle

    1 year ago
    Thanks. I will try that. Cheers
    Cool. I think I got something usable.
    private fun Modifier.animatedXAxisPeak(offset: Float) = layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)
    
        layout(placeable.width, placeable.height) {
            placeable.place(x = ((offset - (placeable.width)).toInt()), y = 0)
        }
    }
    I haven't figured out how to do the animated offset yet. I thought this would work, but I'll keep trying.
    Box() {
        val offset = remember { Animatable(0F) }
    
        if (currentPage == 1) {
            offset.targetValue = 50F
        } else if (currentPage == 2) {
            offset.targetValue = 100F
        }
    
        Image(
            modifier =
                Modifier.height(140.dp)
                    .animatedXAxisPeak(offset = offset.value),
    Albert Chang

    Albert Chang

    1 year ago
    You should use
    animateTo()
    on the
    Animatable
    . Also if your animation is state-based you may want to use
    animateFloatAsState
    .
    Nader Jawad

    Nader Jawad

    1 year ago
    You should be able to pass an
    Alignment
    parameter to the
    Image
    composable with a value of -1, 0 for the horizontal and vertical alignment respectively. This will align the image to the left side within the bounds of the Image composable. An alignment of 0,0 means to center the contents of the drawn image within the composable bounds. Alignment is intended to be used for positioning or parallax effects within the Image composable.
    Colton Idle

    Colton Idle

    1 year ago
    Oooh. Interesting. That could work as well as I very much want this to be a percentage based thing. I will try that now Nader. @Albert Chang I was able to get it to move/animate successfully with your help though.
    Box {
        val offset = remember { Animatable(0F) }
    
        val scope = rememberCoroutineScope()
    
        if (currentPage == 1) {
            scope.launch { offset.animateTo(50F) }
        } else if (currentPage == 2) {
            scope.launch { offset.animateTo(100F) }
        }
    
        Image(
            modifier = Modifier.height(140.dp).animatedXAxisPeak(offset.value),
    The only issue is the "animateTo" calls are just guesses of mine. I'm going to see if the alignment param makes things easier.
    @Nader Jawad how do you rec passing in an alignment param?
    Image(
        alignment = Alignment(-1, 0),
    ?
    m

    ms

    1 year ago
    @Colton Idle try
    BiasAlignment
    Nader Jawad

    Nader Jawad

    1 year ago
    @Colton Idle that's correct.
    Alignment(-1, 0)
    as you had in your snippet
    Colton Idle

    Colton Idle

    1 year ago
    Hm. Maybe that api changed Nader as that doesn't compile.
    Nader Jawad

    Nader Jawad

    1 year ago
    Since the alignment parameters are just floats you should be able to animate them using animatable
    Sorry that got renamed since the last time I looked at it. It should be
    BiasAlignment
    as @ms suggested
    Alignment
    is the interface and
    BiasAlignment
    is an implementation of that interface
    Colton Idle

    Colton Idle

    1 year ago
    Hm
    Image(
        alignment = BiasAlignment(-1F, 0F),
        modifier = Modifier.height(140.dp),
    but the image still showed up dead center 🤔
    Nader Jawad

    Nader Jawad

    1 year ago
    You will probably also need to change the ContentScale parameter configured on the Image composable. By default it is set to
    ContentScale.Fit
    which will automatically scale the image to fit within the bounds of the composable which won't end up needing to handle any alignment. As you mentioned earlier that the height is to be fixed and the image is expected to be wider than the height, you could use
    ContentScale.FillHeight
    instead. This will ensure the image fills the height of the Image composable but will crop the width of the image being drawn. This is positioned by the alignment parameter you have provided as well.
    Albert Chang

    Albert Chang

    1 year ago
    @Nader Jawad Is it possible to align the image completely out of the bounds using
    BiasAlignment
    ?
    Nader Jawad

    Nader Jawad

    1 year ago
    I believe we intentionally did not bound the BiasAlignment API to prevent rendering completely out of bounds. I'm not by my computer at the moment to verify
    So it should be possible
    Colton Idle

    Colton Idle

    1 year ago
    This also did not work.
    Image(
        alignment = BiasAlignment(-1F, 0F),
        contentScale = ContentScale.FillHeight,
        modifier = Modifier.height(140.dp)
    I will keep trying different content scales...
    Albert Chang

    Albert Chang

    1 year ago
    @Nader Jawad Yeah I think the image can be rendered out of bounds but the problem is what bias you should use when you want it be completely out of bounds. I think the bias value can only be calculated based on image width and available space.
    Nader Jawad

    Nader Jawad

    1 year ago
    @Colton Idle What is the size of the image asset that you are trying to render? If the contents of the image are smaller than the dimensions given to the composable, there is nothing to align as it can fit within the composable bounds so there isn't a need to pan the content. Might be worth experimenting with a smaller composable height to verify.
    m

    ms

    1 year ago
    I think the Image us taking required size, try to make it take full width so that it can align itself
    Nader Jawad

    Nader Jawad

    1 year ago
    @Albert Chang the bias value is always normalized and based off of the size of the image itself
    Colton Idle

    Colton Idle

    1 year ago
    The image is actually this. lol And it's technically wider than the device. But I'm trying to have the picture not be on the screen, and every step of a 8 step flow I move it across the screen until it fully dissapears on the other side. 😄
    Nader Jawad

    Nader Jawad

    1 year ago
    -1 will left align the image so you would end up seeing the "cat" of catdog (wow that's a throwback, I remember watching that show as a kid). Might be worth experimenting with values smaller than -1 (ex. -2 etc.) to get the desired positioning. Also might be worthwhile to share the full composable source you're working with if you can.
    Colton Idle

    Colton Idle

    1 year ago
    I set a height on the image in dp, because I want the width to auto scale correctly (which works), but now I want to place it off the screen, and then move it from the left of the screen to the right (off the screen as well), hence why percentages would work nicely.
    Box() {
        Image(
            alignment = BiasAlignment(-1F, 0F),
            contentScale = ContentScale.FillHeight,
            modifier = Modifier.height(140.dp),
            painter = painterResource(id = com.myapp.myresources.R.drawable.catdog),
            contentDescription = null)
    }
    This is what I have currently, and it seems like it's just smack in the middle of the screen. I will try with -2 etc, as Nader suggested.
    Albert Chang

    Albert Chang

    1 year ago
    @Nader Jawad What I wanted to say is that I guess you can use some values to put the image at some point left of bounds but I don't think it's possible to put it just out of bounds (the behavior of
    placeable.place(-placeable.width, 0)
    ).
    Colton Idle

    Colton Idle

    1 year ago
    Interesting. 10f almost works. As in... the dog is almost completely off the left edge of the screen. Can see a bit of his purple nose. Weird. I thought for sure the float here would be percantage based out of 100, but at this point idk what's going on.
    Okay. This "works". Again, a little hacky because 10f isn't perfectly off the screen. I will need to play around to see what 100% off the screen is and then test on multiple devices to make sure it holds true on them.
    Box {
        val offset = remember { Animatable(10F) }
    
        val scope = rememberCoroutineScope()
    
        when (currentPage) {
            0 -> scope.launch { offset.animateTo(10F) }
            1 -> scope.launch { offset.animateTo(7F) }
            2 -> scope.launch { offset.animateTo(5F) }
            3 -> scope.launch { offset.animateTo(2F) }
            4 -> scope.launch { offset.animateTo(-5F) }
            5 -> scope.launch { offset.animateTo(-8F) }
        }
    
        Image(
            alignment = BiasAlignment(offset.value, 0F),
            contentScale = ContentScale.FillHeight,
            modifier = Modifier.height(140.dp)
    Nader Jawad

    Nader Jawad

    1 year ago
    @Albert Chang in this case, the Alignment parameter is not used as part of layout, but rather translates the drawscope to draw the image within the previously sized bounds. The Alignment parameter only affect drawing of the painter provided to the Image composable but not the sizing
    Colton Idle

    Colton Idle

    1 year ago
    Yeah, on my physical device, I need to change the 10 to a 60 to get the same effect. I guess I can't use this method. edit: unless this is a bug with biasAlignment. I guess I could file a bug?
    Nader Jawad

    Nader Jawad

    1 year ago
    I don't think this is an issue with BiasAlignment per se. It starts by calculating the center point of the size of the image within the bounds of the composable and shifts to the left or right based on values from -1 to 1. So -1 ends up translating the contents right by 50% of the image size. Would have to work backwards to calculate the bias for it to be fully off screen
    Albert Chang

    Albert Chang

    1 year ago
    By the way you can make the offset percent-based using the layout way. Something like this should work:
    val offsetPercent by animateFloatAsState(
        targetValue = when (currentPage) {
            0 -> 0f
            1 -> 0.2f
            2 -> 0.4f
            3 -> 0.6f
            4 -> 0.8f
            else -> 1f
        }
    )
    Image(
        painter = painterResource(id = com.myapp.myresources.R.drawable.catdog),
        contentDescription = null,
        modifier = Modifier
            .height(140.dp)
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints)
                layout(constraints.maxWidth, placeable.height) {
                    placeable.place(
                        x = lerp(-placeable.width, constraints.maxWidth, offsetPercent),
                        y = 0
                    )
                }
            }
    )
    Colton Idle

    Colton Idle

    1 year ago
    @Albert Chang which lerp import did you use?
    Albert Chang

    Albert Chang

    1 year ago
    androidx.compose.ui.util.lerp
    in
    androidx.compose.ui:ui-util
    . Or you can just copy the function:
    fun lerp(start: Float, stop: Float, fraction: Float): Float {
        return (1 - fraction) * start + fraction * stop
    }
    Colton Idle

    Colton Idle

    1 year ago
    Thanks. That almost works. The face of the cat (left side of image) got chopped off though. Interesting. Need to get some sleep. Stuff like this makes me feel like I still don't understand compose anims at all, but I'm sure I'll get it eventually.
    Another small note. Going alberts approach also pins the image to the bottom of the bounding box throwing off my spacing/padding on top of the image. /shruggie
    Albert Chang

    Albert Chang

    1 year ago
    Ok so your image is wider than the screen. In that case you might want to measure it with a infinite width instead:
    measurable.measure(constraints.copy(maxWidth = Constraints.Infinity))
    .
    Nader Jawad

    Nader Jawad

    1 year ago
    I think the simplest approach will be to create your own alignment implementation that will give you the most fine grained control with the given image size and the Image composables dimensions. Something like the following that you configure on the Image composable's alignment property:
    val offscreenAlignment = object : Alignment {
                override fun align(
                    size: IntSize,
                    space: IntSize,
                    layoutDirection: LayoutDirection
                ): IntOffset {
                    return IntOffset(-size.width, 0)
                }
            }
    Note you don't want to change any of the layout properties, but rather you are defining how to position the png that you are drawing within the bounds/layout of the composable itself. You're effectively "panning" the png within the composable so changing constraints/layout here is not going to generate the desired effect
    Colton Idle

    Colton Idle

    1 year ago
    Ah. Thank you both @Albert Chang your solution worked and Nader your latest solution worked as well! I'm able to easily go the percentage route with both solutions, so now catdog is happily walking through the screen. Thank you again for dealing with this little exercise. I learned a ton from you all!