Stylianos Gakis
08/16/2022, 10:00 PMBox {
SomeScrollableContent(Modifier.fillMaxSize())
BottomAnchoredItem(Modifier.align(Alignment.BottomCenter))
}
Is there a way to make sure that my scrollable content is providing enough extra space at the end of the scrollable content so that when I’m scrolled all the way to the bottom the items are over this bottom anchored item?@Composable
fun MyBox() {
Box {
val density = LocalDensity.current
var bottomAnchoredItemHeight by remember { mutableStateOf(0.dp) }
SomeScrollableContent(bottomAnchoredItemHeight, Modifier.fillMaxSize())
BottomAnchoredItem(
Modifier
.align(Alignment.BottomCenter)
.onPlaced { layoutCoordinates ->
bottomAnchoredItemHeight = with(density) { layoutCoordinates.size.height.toDp() }
},
)
}
}
@Composable
fun SomeScrollableContent(bottomAnchoredItemHeight: Dp, modifier: Modifier = Modifier) {
Column(Modifier.verticalScroll(rememberScrollState()).padding(bottom = bottomAnchoredItemHeight)) {}
}
But since I’m relying on onPlaced
to change some state this happens 1 frame late, and in general makes me feel like I’m missing a better approach.Francesc
08/16/2022, 10:09 PMColumn
with the scrollable content having a weight?Stylianos Gakis
08/16/2022, 10:14 PMFrancesc
08/16/2022, 10:21 PMColumn
, the anchored content will still be at the bottom, the scrollable content will use all the remaining space after the anchored item is measured, which seems to be what you wantStylianos Gakis
08/16/2022, 10:26 PMFrancesc
08/16/2022, 10:27 PMLayout
thenStylianos Gakis
08/16/2022, 10:30 PMLayout
🤷♂️Francesc
08/16/2022, 10:32 PMSubcomposeLayout
might be the answer here as you can use a child's size as input for another childStylianos Gakis
08/16/2022, 10:56 PMAlex Vanyo
08/16/2022, 11:25 PMScaffold
here, which uses SubcomposeLayout
under the hood to be able to provide the size of the bottom bar to the main content.
I’d prefer that over the 1 frame delay (due to a cyclic composition dependency). SubcomposeLayout
is more costly in general, so you’d want to avoid using it everywhere, but this is one of those cases where you can use it to have the size of one part of your layout inform what the composition should be in a different part.Stylianos Gakis
08/17/2022, 12:13 AMSubcomposeLayout
.
Does scaffold provide a slot for my use case? bottomBar
maybe? 🤔
Plus I guess I’d have to use the one provided by accompanist insets-ui since I’m going edge to edge in this screen.Alex Vanyo
08/17/2022, 12:22 AMScaffold
is probably going to do a bit more than you need, but you can try it out and then you may want to roll your own SubcomposeLayout
. This should be about all you need:
Scaffold(
bottomBar = {
BottomAnchoredItem(Modifier.align(Alignment.BottomCenter))
}
) { innerPadding -> // inner padding will now have the height of the BottomAnchoredItem
SomeScrollableContent(Modifier.fillMaxSize())
}
You’d only need the accompanist/insets-ui one to do to the analogous thing for the topBar
with the Material 2 Scaffold
. (the default Material 2 Scaffold
doesn’t include the topBar
height in the innerPadding
), but the one in accompanist/insets-ui does)Stylianos Gakis
08/17/2022, 8:37 AMBottomAnchoredItem
like this (No need for the Alignment.BottomCenter
anymore, since we’re not in a BoxScope
so we can’t even access it if we wanted to):
BottomAnchoredItem(
modifier = Modifier
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal))
.padding(16.dp),
) {
Text(text = data.callToAction)
}
Then the paddingValues
I get do in fact contain a ~100dp bottom padding which I can then use like .verticalScroll(rememberScrollState()).padding(paddingValues)
on my column and it’s working perfectly fine!.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom))
on my ScrollableContent too, in order to avoid the bottom bar, but since I’m doing that in my BottomAnchoredItem
too, now that spacing is part of paddingValues
provided by the Scaffold, it double applies that value 🙈
But then I remember you were doing something like this in the episode, so I tried adding consumedWindowInsets
as a modifier to my scaffold.
Scaffold(
bottomBar = {
BottomAnchoredItem(
modifier = Modifier
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal))
.padding(16.dp),
)
},
modifier = Modifier.consumedWindowInsets(
WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal),
)
) { paddingValues ->
This did not work as that now also consumed the paddings that the BottomAnchoredItem
wanted to use.
Moved that modifier down to the one child of the Scaffold
content
val bottomAnchoredButtonInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal)
Scaffold(
bottomBar = {
BottomAnchoredItem(
modifier = Modifier
.windowInsetsPadding(bottomAnchoredButtonInsets)
.padding(16.dp),
)
},
modifier = Modifier.consumedWindowInsets(bottomAnchoredButtonInsets)
) { paddingValues ->
Box(Modifier.consumedWindowInsets(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal))) {}
}
And what do you know, this does work 🤯🤯🤯🤯 without having to go in that other composable and remove the insets in case idk, this changes sometime, or reusing that other component somewhere else etc.
I guess this was not an exaggeration after all, that CWTI episode taught me so much about the inset APIs, I never would’ve even thought of doing smth like this before. Thanks a lot for that and the help here Alex! 🙌bottomBar
content on the Scaffold is not rotating with the phone obviously, I still need the horizontal paddings to propagate down to the child. Fixed it now by making the insets passed to consumedWindowInsets
only take the WindowInsetsSides.Bottom
so only considering those as consumed. Starting to get a bit tricky at this point, but I think I’m done now 😂 It all seems to work just fine.consumedWindowInsets
is considered experimental now. Could you share why that is? Is the API surface still being considered as unstable, or are there any known issues with it or something else?Alex Vanyo
08/17/2022, 3:54 PMcontentPadding
, and how to provide the insets in such a way that it respects the consumed insets: https://issuetracker.google.com/issues/237019262
WindowInsets.ime
will return the underlying, full insets values, and related to that WindowInsets.ime.asPaddingValues()
won’t interact with consumedWindowInsets
. There probably should be some way to interop there, either to get how many insets have been consumed so far (to use to subtract) or provide padding values that would interact with consumedWindowInsets
.