I'm kind of going crazy on not being able to get t...
# compose
c
I'm kind of going crazy on not being able to get this layout to look quite right in my app. Essentially, the top horizontal bar is a carousel made up of RedComposable's, and then there is a green composable list, which is a vertical scrolling column. The GreenComposable includes a RedComposable. The trouble I'm having is how to size the RedComposable. My design team said "The Red should be 40% of the green, and then that size of the red should be used in the horizontal scroller" How would I do that in compose?
j
I would start with measuring column width (eq.
onSizeChanged
), then I would use that size to compute size of GreenComposable (eg. subtract column paddings, ..), then I would derive RedComposable from that. If the performance is not good enough (there will be unnecessary layout passes due to laziness of this solution), I would try to create
SubcomposeLayout
to see if can spare some layout passes.
Actually, you could probably just wrap your layout into
BoxWithConstraints
and compute sizes of column, red and green from that
1
c
Assuming you only care about screen width, you could query
LocalConfiguration.current.screenWidthDp
to get the device width, then do a calculation based on the horizontal padding you plan to have on the green Composables list. Save that as a variable hoisted to a place in your hierarchy that can control the top Red container and the green list, and go from there
a
You can just use
Modifier.fillParentMaxWidth(fraction = 0.4f)
in a
LazyRow
.
c
I think though that value needs to be then passed to size the red Composables in the top container
a
I mean use that in the top container:
Copy code
LazyRow(
    modifier = Modifier.fillMaxWidth(),
    contentPadding = PaddingValues(16.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    items(10) { i ->
        Text(
            text = (i + 1).toString(),
            modifier = Modifier
                .fillParentMaxWidth(fraction = 0.4f)
                .height(100.dp)
                .background(color = Color.Red.copy(alpha = 0.2f))
                .wrapContentSize()
        )
    }
}
K 2
c
Oh yeah good call!
c
Hm. And then my GreenComposable would just fill the max width (with 16 horizontal padding), and the RedComposable would again be set to .4f width of the GreenComposable? So in that solution @Albert Chang I'm not actually "saving" a width value anywhere for green and reusing it... I "just so happen" to get the same width through a use of width modifiers?
a
Yes. Note that you need to adjust the paddings in order for them to be exactly the same width:
Copy code
LazyRow(
    modifier = Modifier.fillMaxWidth(),
    contentPadding = PaddingValues(horizontal = 16.dp * 0.8f, vertical = 16.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp * 0.6f)
) {
    items(10) { i ->
        Text(
            text = (i + 1).toString(),
            modifier = Modifier
                .fillParentMaxWidth(fraction = 0.4f)
                .height(100.dp)
                .padding(horizontal = 16.dp * 0.2f)
                .background(color = Color.Red.copy(alpha = 0.2f))
                .wrapContentSize()
        )
    }
}
c
Hm. You lost me at adjusting the paddings...
Copy code
16.dp * 0.2f
and
Copy code
16.dp * 0.8f,
?
a
Modifier.fillParentMaxWidth(fraction = 0.4f)
makes the width 40% of screen width, however you want it to be 40% of (screenWidth - 16dp), so you need to add a padding of (16dp * 20%) on both sides, and accordingly substract that from contentPadding and subtract 2 * that from spacing between items.
c
Hm. I don't think that extra calculation is needed?
The top LazyRow is the first snippet of code, the second LazyRow is the second snippet (with the additional padding multipliers)
a
Oh yeah, seems that the parent size is already the size with contentPadding applied.
c
Awesome. Thought I was going crazy. That works sort of nicely then.
The last thing is that the design team wants to name that RedComponent, and use it in both layouts, but I don't think that'll work because it needs different modifiers.
So there's kind of an illusion going on that it's the same "exact" component being used... even though it's not?
To my point. Here's my final code, but trying to extract it into a single composable doesn't give the result that my design team is thinking (i think)
Copy code
Column {
    LazyRow(
        modifier = Modifier.fillMaxWidth(),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        items(10) { i ->
            Text(
                text = (i + 1).toString(),
                modifier = Modifier
                    .fillParentMaxWidth(fraction = 0.4f)
                    .height(100.dp)
                    .background(color = Color.Red.copy(alpha = 0.2f))
                    .wrapContentSize()
            )
        }
    }
    repeat(3) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        ) {
            Text(
                text = ("left 40%").toString(),
                modifier = Modifier
                    .weight(0.4f)
                    .height(100.dp)
                    .background(color = Color.Red.copy(alpha = 0.2f))
                    .wrapContentSize()
            )
            Text(
                text = ("right 60%").toString(),
                modifier = Modifier
                    .weight(0.6f)
                    .height(100.dp)
                    .background(color = Color.Green.copy(alpha = 0.2f))
                    .wrapContentSize()
            )
        }
    }

}
a
They can and should be the same component. You should always provide a Modifier parameter.
1
c
I read that article this past weekend. But yeah, I think maybe lemme give a whirl of how itd look like if it was shared.
Okay. I created this
Copy code
@Composable
fun RedComposable(text: String, modifier: Modifier) {
    Text(
        text = text,
        modifier = modifier
            .height(100.dp)
            .background(color = Color.Red.copy(alpha = 0.2f))
            .wrapContentSize()
    )
}
Which basically is as "skinny" as the text. Which is just a little contradictory to how I think my design team thinks about it. I.E: My design team has a component library and this RedComposable always has the same size in the library, but I think that's just a the price I gotta pay in order to make a composable reusable in multiple places.
I'd also like to say that I still don't understand what the heck wrapContentSize does. lol
c
You can always set the default Modifier in your function parameter to
Modifier.width(<usual size>)
So if no Modifier is passed it looks like a consistent width
Or you could also pass a default string to get to the size the design team expects most of the time
Also since this component seems to be just Text, I don't think you need that wrapContentSize modifier because that is only really applied for width, since you have height always at 100.dp
👍 1
c
@Chris Sinco [G] for "You can always set the default Modifier in your function parameter to 
Modifier.width(<usual size>)
" Where would I put that? In the RedComposable?
c
Yes the signature of the RedComposable function
Then you can always override the width where you use the RedComposable based on the context / parent container
c
Oh. I think I know what you mean. So you mean like this
Copy code
@Composable
fun RedComposable(text: String, modifier: Modifier.width(200.dp)) {
    Text(
        text = text,
        modifier = modifier
            .height(100.dp)
            .background(color = Color.Red.copy(alpha = 0.2f))
            .wrapContentSize()
    )
}
👍 1
awesome. didn't even think of that. okay. cool. I have some avenues to pursue here. 🤞
🎉 1
a
wrapContentSize()
(with the
align
parameter set to its default value
Alignment.Center
) aligns the text at the center. Without that the text will be placed at the top start. You can also use
Text(textAlign = TextAlign.Center)
to align the text at the center horizontally but that doesn’t change vertical alignment.
c
ooh. interesting. thanks albert.
After playing around with this for a few hours I do have to say that I really like the approach of using
LocalConfiguration.current.screenWidthDp
as I can calculate the value once and pass it in which makes the intent a bit easier to understand to me. Overall, I'm just happy that I didn't have to go with BoxWithConstraints 😄
c
Haha well you can still use the RedComposable as a component and pass whatever value you want in, even if that's screenWidthDp ;)
a
the screen width approach will completely nuke any drop in reusability of the component though, just something to keep in mind for future tech debt, when using this screen in a dialog / on the desktop or a tablet in a column etc
In effect I want to say that the screen width approach is not a proper solution to the problem but more of a quick fix 😄 Not to say that it's not a valid quick fix considering the cirumstances maybe, but still kind of a quick workaround
c
What's your suggested solution to it?
z
Probably
BoxWithConstraints
👍 2
t
Running into a similar use case, came across this thread. @Colton Idle its been a while but do you recall what your solution was? (to make matters worse, for me everything is supposed to live in a
LazyColumn
) 🙏🏼
c
IIRC and my biggest thing I took away from this was the fact that I stopped sizing my composables inside of the composable. Basically what @Chris Sinco [G] said
Then you can always override the width where you use the RedComposable based on the context / parent container
So a technique I was mostly taking was that "hey wouldn't it be convenient if this composable always just knew it should be fill max width" and after chris' comments and the rest of the comments above I basically conceded to the fact that it's kinda like not doing dependency injection. Sure. Having a class where I don't have to pass in a bunch of services to make it work correctly is convenient, but its extremely limiting. So yeah. I basically typically have composables take up the least amount of space possible, and then have a modifier so it can be adjusted based on its context.
also @Tash i mightve misconstrued your question overall. but let me know if you have anything specific. in regards to the first example I drew out in the orinal post, I think I could recreate that pretty easily now in like 15 minutes.