user2100776
user2100776

Reputation: 81

How to conditionally add a Composable based on its size?

Let's say I have some parent Composable such as a BoxWithConstraints and I know the size in dp of this Box. Then lets say I have a child Composable which I would like to only display/(add to the layout) if the child Composable's size would fit within the size of the parent composable.

How could I achieve this? Basically my two questions are, how can I measure the child without displaying it, and then use that measured size to conditionally add the child to the layout.

@Composable
fun PseudoView() {
    BoxWithConstraints(
        Modifier.size(width = 200.dp, height = 100.dp)
    ) {
        val childSizeIfLaidOut = somehowGetTheSizeWithoutShowingTheChild()
        val (childWidth, childHeight) = childSizeIfLaidOut 
        if (childWidth <= 200.dp && childHeight <= 100.dp) {
            Child()
        }
    }
}

I was trying something along the lines of first trying to add the Child using a Modifier which included .onSizeChanged, and .drawWithContent{} in order to measure the child first and then use those for the conditional like.

@Composable
fun PseudoView() {
    BoxWithConstraints(
        Modifier.size(width = 200.dp, height = 100.dp)
    ) {
        var childSize = placeholder
        Child(modifier = Modifier
            .wrapContentSize()
            .onSizeChanged { childSize = it }
            .drawWithContent {} // Only use this child for measuring 
        )
        val (childWidth, childHeight) = childSize 
        if (childWidth <= 200.dp && childHeight <= 100.dp) { // Wrong results for boolean.
            Child()
        }
    }
}

However I don't get the expected sizes to evaluate the right result in the conditional.

Upvotes: 0

Views: 618

Answers (2)

user2100776
user2100776

Reputation: 81

Okay, after playing around a bit deeper with compose, I found the following solution that solves my particular issue. The idea was to just create a custom layout which conditionally adds the child if it fits in the layout.

@Composable
fun ConditionalLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        val placeables = mutableListOf<Placeable>()
        for (measurable in measurables) {
            val myConstraints = // Create Constraints based on your requirements.
            val placeable = measurable.measure(myConstraints)
            if (placeable.width <= constraints.maxWidth && placeable.height <= constraints.maxHeight) {
                placeables.add(placeable)
            }
        }
        layout(constraints.maxWidth, constraints.maxHeight) {
            placeables.forEach { placeable ->
                // Place placeables.
            }
        }
    }
}

Upvotes: 2

Jan B&#237;na
Jan B&#237;na

Reputation: 7278

You are pretty much there, you just have to convert dp value to pixels or vice versa, since onSizeChanged gives you pixels and you want to specify the max size in dp. Also, you don't have to call the composable twice - you can create it invisible at first and then show it once you know it fits:

var childSize by remember { mutableStateOf<IntSize?>(null) }
val density = LocalDensity.current

Child(
  modifier = Modifier
    .onSizeChanged { childSize = it }
    .drawWithContent {
      density.run {
        childSize?.let {
          if (it.width <= 200.dp.toPx() && it.height <= 100.dp.toPx()) {
            drawContent()
          }
        }
      }
    }
)

Upvotes: 0

Related Questions