Is there a way for a composable to render outside ...
# compose
s
Is there a way for a composable to render outside of the parent's clip path? Use case is that some parent is doing some clipping which I do not want to happen but I have no control over. Compose does not clip by default and this is like never a problem, but in this particular case I don't get this benefit. Is there just any way for me to completely go against the parent's requirements for this and just render outside, no matter how hacky the solution might be I just wanna figure out if it's possible.
Use case is that for some reason all scrollable content in compose just decided to clip after an arbitrary distance on the cross axis by force-adding this without an option to remove https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]bleClipModifier&ss=androidx%2Fplatform%2Fframeworks%2Fsupport and I just can not find a way to disregard that and render outside of this clipped area.
e
nope, clipping is additive (subtractive?) only. you'll have to not be a child to draw outside of the "parent"'s clip bounds
kodee sad 1
from that bit of code, does this not work for you?
If the user will need to have a larger unclipped area for some reason they can always add the needed padding inside the scrollable area.
s
I do not wish to have my scrollable content take more than the space it is already taking is the problem. To try to explain the UI needed, think of a horizontally scrollable list of items which when click are supposed to have a little animation run right above them, think like the messenger reaction in the picture attached. The row of items should stay as big as it is, if padding is added to it then it will push other things further up and this is not what I want to do here.
I am now achieving this by doing smth like this:
Copy code
Box {
  Box(
    Modifier
      .align(Alignment.TopCenter)
      .matchParentSize()
      .requiredHeight(0.dp)
      .wrapContentHeight(Alignment.Bottom, true)
      .requiredHeight(72.dp)
      .border(1.dp, Color.Red),
    propagateMinConstraints = true,
  ) {
    AnimationItem()
  }
  Chip(item)
}
So the Chip at the bottom is the one that takes the real size, and the animation item above is just supposed to start from right above it in the middle, and go up as much as it needs, hence the
wrapContentHeight(Alignment.Bottom, true)
but this plan was foiled by this non-optional modifier in there when I made my container be scrollable. The problem is that I understand why the clip is there on the main axis. But why is there a clip on the cross axis? If someone wants to add taller items than fit in the horizontal list, I do not see how the clip would make them look good anyway. Is it a limitation since they can not add a clip on the main axis without adding some clip on the cross axis too? Since they can't perhaps just add an infinite value there
e
add padding inside then compensate for it outside?
padding
doesn't take negative values but you can make your own
Copy code
Modifier.layout { measurable, constraints ->
    val placeable = measurable.measure(constraints.offset(start + end, top + bottom))
    val width = constraints.constrainWidth(placeable.width - start - end)
    val height = constraints.constrainHeight(placeable.height - top - bottom)
    layout(width, height) { placeable.placeRelative(-start, -top) }
}
s
Thanks a lot for the idea. Idk if I am misusing what your intention was with this, but with this code:
Copy code
@Composable
fun SlackTest() {
  Box(Modifier.fillMaxSize(), Alignment.Center) {
    Row {
      Hack(withHack = false)
      Hack(withHack = true)
    }
  }
}

@Composable
fun Hack(withHack: Boolean) {
  Column {
    Column(
      if (withHack) Modifier.paddingAllowingNegatives(bottom = animationLayoutHeight) else Modifier
    ) {
      Text("I am text that might be hidden tbh")
      Text("Me too!")
      Text("Me three!")
    }
    val scrollState = rememberScrollState()
    Row(
      Modifier
        .border(1.dp, Color.Black)
        .horizontalScroll(scrollState)
    ) {
      for (item in items) {
        Column {
          if (withHack) {
            Spacer(Modifier.height(animationLayoutHeight))
          }
          Box {
            Box(
              Modifier
                .align(Alignment.TopCenter)
                .matchParentSize()
                .requiredHeight(0.dp)
                .wrapContentHeight(Alignment.Bottom, true)
                .requiredHeight(animationLayoutHeight)
                .border(1.dp, Color.Red),
              propagateMinConstraints = true,
            ) {
              AnimationItem()
            }
            Chip(item)
          }
        }
      }
    }
  }
}
@Composable
private fun Chip(item: String, modifier: Modifier = Modifier) {
  // Replace with real chip content
  Card(modifier, shape = CircleShape) {
    Text(item, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp))
  }
}

@Composable
fun AnimationItem(modifier: Modifier = Modifier) {
  // Here would be the Lottie thing instead
  Box(modifier.background(Color.Cyan)) {
    Text("AA")
  }
}
I get this result [pic] and well, I suppose it works, with the caveat of putting everything in the right spot and very carefully 😄
Also I suppose instead of this special layout modifier, I could just make a custom layout for that screen, and layout the chips+anim item where it should be, and lay out the items above offset by the animation height instead. I would feel a bit more comfortable with that approach I think. Regardless, I +1 this issue which looks quite old already without much action https://issuetracker.google.com/issues/308510944 because I am curious if this could ever change in the future