In a Layout that receives the `scrollable` Modifi...
# compose
t
In a Layout that receives the
scrollable
Modifier, the
constraints.maxWidth
is automatically set to Infinity, which makes sense as the Composable is infinitely scrollable. However, for the layout I'm currently building I also need to get the width of the Layout that is visible. So say for example that if my Layout is vertically Scrollable and used in a Column that spans the whole screen and has a horizontal padding of 16Dp, that would make the visible area
screenSize - 2*16Dp
wide. Is there any way to get the size of this visible area using a
Layout
or
SubcomposeLayout
?
solved 1
I solved it, by wrapping my layout with a
BoxWithConstraints
and then settings the
maxWidth
of the
BoxWithConstraints
as the
minWidth
of my Layout, then resetting the constraints to have a
minWidth
of 0, as to not mess up measurement of the children of my layout like so:
Copy code
BoxWithConstraints {
        SubcomposeLayout(
            modifier = modifier.defaultMinSize(this.maxWidth)
        ) { constraints ->
            val visibleArea = constraints.minWidth
            val constraints = constraints.copy(minWidth = 0)
        }
}
However, this feels like a super hacky solution. Are there any better ways?
s
Why not have your component not have this 16.dp padding externally, but make itself span the entire width, and put the padding in the contents of it. Kinda like what
LazyColumn
does by having the
contentPadding
parameter?
t
That would work if I could be certain that my Layout will always be the child of a Composable that spans the whole screen. However, this Layout may also be used in Contexts, where it is impossible for the parent to span the whole screen.
s
Fair enough. Mind if I ask here then, what is it that makes it important for that child in particular to need to know the entire screen’s width?
And with that said, one thing I’ve started doing recently was not provide screen-wide paddings, but only apply them to the children as needed. This has been super useful since you often want one thing to span the entire width (if it’s scrolling horizontally for example). Or in other cases it improves animations, with things being allowed to go to the edge as they’re coming in, or if their scale is being animated etc. Gives a lot of freedom to screens to do their own thing
t
It's not so much about the width of the screen, as about the width the Layout actually takes up on the screen Say I want to build a row of
n
elements, where n is unknown. If the Elements do not completly fill the Width the Layout takes up, I want to evenly space them across the Width of my Layout. If all the Elements are larger than the width the Layout takes up, I want them to be spaced by a predefined value and have the row be scrollable. Kind of like in the attached sketch. (This sketch does not capture the full complexity of the requirements of the layout, but do get the point across. A normal Row would unfortunately not do the job)
And with that said, one thing I’ve started doing recently was not provide screen-wide paddings, but only apply them to the children as needed.
I have gotten into this habit myself due to the same reasons you stated 🙂 Was just trying to give a simple (even if flawed) example 😅
s
Could all this be done just with some extra paddings and weighted spacers? Something like this
Copy code
Row {
  Spacer(Modifier.fillMaxHeight().width(20.dp).height(50.dp).debugBorder())
  val intRange = 0..7
  Row(Modifier.weight(1f).horizontalScroll(rememberScrollState())) {
    Spacer(Modifier.width(8.dp))
    Spacer(Modifier.weight(1f))
    for (i in intRange) {
      Item()
      if (i != intRange.last) {
        Spacer(Modifier.weight(1f))
        Spacer(Modifier.width(8.dp))
      }
    }
    Spacer(Modifier.weight(1f))
    Spacer(Modifier.width(8.dp))
  }
  Spacer(Modifier.fillMaxHeight().width(20.dp).height(50.dp).debugBorder())
}

@Composable
private fun Item(modifier: Modifier = Modifier) {
  Box(modifier.size(50.dp).background(Color.Red, CircleShape))
}
Example outputs when I change the X here
val intRange = 0..X
The combination of weight + width is the trick where it takes the weighted space (equal for all places) if there is enough space (1-4 balls) but it takes nothing if there are a lot of balls, since the layout already takes up the entire space, so weight(1f) means take 0 width. That’s where the width(8.dp) kicks in and does always take the necessary space. 8.dp there. That’s the “predefined space” in your sentence
I want them to be spaced by a predefined value and have the row be scrollable.
t
I guess my examples may not be good 😅 I still need the area my Layout takes up on the screen, to properly handle the case where the size of the content of my layout does not exceed the visible area of my layout. Just like I've done with the
BoxWithConstraints
hack. I guess I just want to know whether there is a less hacky way to do this
s
So "other composable of varying sizes" is the thing that should also resize according to the number of balls? Those don't just take as much as they want anyway?
z
The best way is to put an
onSizeChanged
modifier right before your
scrollable
modifier and save the size from there.
BoxWithConstraints
is definitely not required.
s
I had this exact same requirement now, but vertically, and putting an
onSizeChanged
right before the scroll modifier was the solution, ah so glad this exists I was not finding any way to fix this. Thanks a lot Zach 🙏