Is it possible to "Scale" width and height any widget in jetpack compose?

16.5k views Asked by At

I have a Stack widget which hosts a Box and an Image. As the state changes, I want to scale the Box widget by whatever value the state has, for example by 2x.

I couldn't find anything about scaling the widgets on the Modifier or Box properties so I decided to react to the state changes by manipulating the size using "Modifier.size" which is not ideal for me.

So is there support for scaling the widgets or should I manually play with the size property?

-Thanks

@Composable
fun Pointer(modifier: Modifier = Modifier, state: TransitionState, onClick: () -> Unit) {
    Stack(modifier) {
        Box(
            shape = CircleShape, backgroundColor = Color.Gray.copy(alpha = .3f),
            modifier = Modifier.size(state[width])
        )
        Image(
            asset = imageResource(id = R.drawable.ic_pointer),
            modifier = Modifier
                .clickable(onClick = onClick)
        )
    }
}
2

There are 2 answers

1
CommonsWare On

I don't want to care about the size of the box or explicitly keep a reference to it

That is going to be a problem. In Compose, widgets like Box() are stateless functions. You cannot ask a Box() how big it is — instead, you need to tell the Box() how big it is, using a suitable Modifier.

Frequently, "how big it is" is a fixed value or rule, set in the code (e.g., Modifier.size(200.dp)). You should be able to have the size be dependent upon some state that you track yourself, so long as that state is a State, so Compose knows to recompose (call your function again) when that State changes. If you go that route, then scaling is a matter of checking the current State value, applying your scale factor, and using the result for the new State value.

0
Alex Balan On

Compose 1.0.0-alpha08

As time passes by, there is a new compose version which renames 'drawLayer' to 'graphicsLayer' and adds a new scale modifier (uses 'graphicsLayer' underneath).

Thus composable will look like:

@Composable
fun Pointer(scale: Float, modifier: Modifier = Modifier) {
    Box(modifier) {
        Box(
            modifier = Modifier
                .matchParentSize()
                .scale(scale)
                .background(Color.Cyan, CircleShape)
        )
        Image(
            imageVector = Icons.Filled.Done,
            modifier = Modifier
                .align(Alignment.Center)
        )
    }
}

Compose 1.0.0-alpha07 (original answer)

I believe you may achive the desired behavior with drawLayer modifier. For example a simple composable, which displays an scalable circle and an unscalable icon on top of it:

@Composable
fun Pointer(scale: Float, modifier: Modifier = Modifier) {
    Box(modifier) {
        Box(
            modifier = Modifier
                .matchParentSize()
                .drawLayer(scaleX = scale, scaleY = scale)
                .background(Color.Cyan, CircleShape)
        )
        Image(
            asset = Icons.Filled.Done,
            modifier = Modifier
                .align(Alignment.Center)
        )
    }
}

And usage:

Pointer(
    scale = 1f,
    modifier = Modifier
        .background(Color.Magenta)
        .padding(25.dp)
        .preferredSize(50.dp)
        .align(Alignment.CenterHorizontally)
)

When scale = 1f

When scale = 2f