Im trying to have a composable scale inside a ConstrainLayout until a specified max size (usually by setting the .sizeIn, .widthIn or .heightIn Modifier) is reached or the borders of the constraints are reached.

Using only .sizeIn, .widthIn or .heightIn will lead to displaying the composable always at the min size.

So we specify .fillMaxSize on the composable to take the maximum amount of space (until the specified maxSize). So far so good..

But now when the ConstraintLayout gets smaller for example due to a smaller screen and therefore the borders of the composable are exceeding the constraintborders, the composable isn't scaled down.

That seems understandable because we haven't specified that the size of the composable should orient itself on the borders of the specified constraints. So we specify height = Dimensions.fillToConstraints and width = Dimensions.fillToConstraints to only use the space emerging from the given constraints.

And here the problem arises. height = Dimensions.fillToConstraints and width = Dimensions.fillToConstraints seem to overwrite the .sizeIn, .widthIn or .heightIn Modifier because now the size of the composable is alway as big as it can be inside the given constraints, not paying attention to the maxWidthor maxHeight specified earlier.

A workaround would be to put that composable inside another one for example a box and configure the Modifier like this:

ConstraintLayout {
      ...
      Box(
            modifier = Modifier.constrainAs(icon) {
                top.linkTo(greeting.bottom) // some composable on top
                bottom.linkTo(menuButtons.top) // some composable on bottom
                start.linkTo(parent.start)
                end.linkTop(parent.end)
                height = Dimension.fillToConstraints
                width = Dimension.fillToConstraints
            }
        ) {
            Icon( // the size responsive composable
                imageVector = Icons.Outlined.RequestQuote,
                contentDescription = null,
                modifier = Modifier
                    .sizeIn(20.dp, 20.dp, 200.dp, 200.dp) // min and max size
                    .fillMaxSize()
                    .align(Alignment.Center)
            )
        }
      ...
}

This would give us the desired behavior. But in my opinion the additionally specified box represents a non needed overhead. So my question is:

Is there a way to acquire this behavior without using a secondary composable only using a specific Modifier configuration?

2

There are 2 best solutions below

0
On BEST ANSWER

Based on the answer of @hoford who provided me with the idea of using the .atMost() and .atLeast() modifiers I came up with this:

ConstraintLayout { 
   ...
   Icon(
     imageVector = Icons.Outlined.RequestQuote,
                contentDescription = null,
                modifier = Modifier.constrainAs(icon) {
                top.linkTo(greeting.bottom) // some composable on top
                bottom.linkTo(menuButtons.top) // some composable on bottom
                start.linkTo(parent.start)
                end.linkTop(parent.end)
                height = Dimension.fillToConstraints.atMost(200.dp).atLeast(20.dp)
                width = Dimension.fillToConstraints.atMost(200.dp).atLeast(20.dp)
            }
    )
1
On

This can be done directly in the constraints of the constraintAs I think what you are looking for is something like

ConstraintLayout { 
   ...
   Icon(
     imageVector = Icons.Outlined.RequestQuote,
                contentDescription = null,
                modifier = Modifier.constrainAs(icon) {
                top.linkTo(greeting.bottom) // some composable on top
                bottom.linkTo(menuButtons.top) // some composable on bottom
                start.linkTo(parent.start)
                end.linkTop(parent.end)
                height = Dimension.preferredWrapContent.atMost(200.dp).atLeast(20.dp)
                width = Dimension.preferredWrapContent.atMost(200.dp).atLeast(20.dp)
            }
    )