BottomBar overlays scaffold content when using LazyColumn - android-jetpack-compose

When I use Scaffold with a long LazyColumn in it's content and some composable in bottomBar parameter I can't see last item of the column because BottomBar overlays it. Is there a way to fix this overlaying?

In your Scaffold, the content has a PaddingValues parameter, you can use it to add the correct padding.
Something like this:
Scaffold(
...
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(
bottom = it.calculateBottomPadding()
)
//.padding(it) // <<-- or simply this
) {
// Your content
}
}

Related

How to implement sliding animation for selector in Jetpack Compose?

I have the following code:
#Composable
internal fun ActivityInputBottomSheet() {
val items = listOf("Calories", "Steps", "Water")
var currentActive by remember {
mutableStateOf(items[0])
}
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.clip(CircleShape)
.background(Color.Red),
verticalAlignment = Alignment.CenterVertically
) {
items.forEach {
Text(
it,
modifier = Modifier
.clip(CircleShape)
.background(if (it == currentActive) Color.Blue else Color.Transparent)
.clickable {
currentActive = it
}
.padding(
vertical = Padding.Padding8,
horizontal = Padding.Padding16
),
color = Color.White,
)
}
}
}
}
Here I implemented a selector with 3 options (when user clicks at the one option it becomes active, and all other become inactive). It works correctly, but I want to add animation of sliding from the one option to another while changing state of selector (for example if user clicks at the first option and then at the third, blue background will slide through the first, then second and stop at third option). How can it be achieved? Thanks in advance for any help!

Jetpack Compose - Scrollable Column and clip to padding

I have a simple screen with scrollable vertical column. It contains some text and images.
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
) {
...
}
The content is scrollable but it clips to defined padding. Meaning when you scroll, you can see that overscroll shadow does not fill the entire screen, but it is bound to the padding. It looks really bad:
In XML world you would use android:clipToPadding="false" to "fill" the container. Is there equivalent of that in Compose?
Got it, apparently order of modifier constraints matters, didn't know that.
Just place padding as last one.
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(16.dp),
) {
...
}

Show keyboard over Scaffold's bottomBar in Jetpack Compose and apply proper inset paddings

I'm using Scaffold for my main screen with a fixed bottomBar that is visible in every screen of the app, and I'm applying the innerPadding of the Scaffold to its content.
I want the keyboard to appear over the bottomBar, and for that I'm applying the imePadding() only to the Scaffold's content.
However, when the keyboard is opened, both the Scaffold's innerPading and imePadding() are applied to the contents padding.
I've tried to go through the Accompanist Insets migration, but no lucky.
Is there anyway that I can prevent it and apply only one or the other?
Here is a piece of my code:
Scaffold(
topBar = { },
bottomBar = { },
modifier = Modifier
.systemBarsPadding()
) { innerPadding ->
Content(
modifier = Modifier
.padding(innerPadding)
.imePadding()
)
}
And this is the result:
With the now, deprecated, Accompanist Insets, I was using the following solution:
val isImeVisible = LocalWindowInsets.current.ime.isVisible
val contentPadding = remember(isImeVisible) {
if (isImeVisible) PaddingValues(top = innerPadding.calculateTopPadding()) else innerPadding
}
According to Accompanist Insets migration, LocalWindowInsets.current.ime should be replaced with WindowInsets.ime.
It doesn't have isVisible for now, until this bug is fixed. Here's how I've re-created it for now:
val WindowInsets.Companion.isImeVisible: Boolean
#Composable
get() {
val density = LocalDensity.current
val ime = this.ime
return remember {
derivedStateOf {
ime.getBottom(density) > 0
}
}.value
}
Usage:
val isImeVisible = WindowInsets.isImeVisible
This should work with your old remember(isImeVisible) code.
Another solution would be to set BringIntoViewRequester to your content inside Scaffold. Then when textField is focused, you could call bringIntoViewRequester.bringIntoView(). This way you wouldn't need to set any paddings.
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(
modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester)
) {
TextField(
value = "",
onValueChange = {},
modifier = Modifier
.onFocusEvent {
if (it.isFocused) {
coroutineScope.launch {
delay(350)
bringIntoViewRequester.bringIntoView()
}
}
}
)
}
Try using something like this (WARNING: consumedWindowInsets is Experimental, but it's working):
Scaffold(
topBar = { },
bottomBar = { },
modifier = Modifier
.systemBarsPadding()
) { innerPadding ->
Content(
modifier = Modifier
.consumedWindowInsets(innerPadding)
.padding(innerPadding)
.imePadding()
)
}

Compose RoundedCornerShape, padding and background colour not working as expected [duplicate]

This question already has answers here:
Jetpack Compose - Order of Modifiers
(5 answers)
Closed last year.
Expectation
Reality
(Please ignore the exact colours;
Outer background colour is purple and inner background colour is red)
Compose code
#Preview
#Composable
fun MyCta() {
MaterialTheme() {
Box(
modifier = Modifier
.clip(RoundedCornerShape(50))
.padding(32.dp)
.background(MaterialTheme.colors.primary)
) {
Text(
"Tap to continue",
Modifier
.padding(8.dp)
.background(Color.Red)
,
color = MaterialTheme.colors.onPrimary
)
}
}
}
Is my expectation off and why?
You want to use the padding after you have specified the background
#Preview
#Composable
fun MyCta() {
MaterialTheme() {
Box(
modifier = Modifier
.clip(RoundedCornerShape(50))
.background(MaterialTheme.colors.primary)
.padding(32.dp)
) {
Text(
"Tap to continue",
Modifier
.padding(8.dp)
.background(Color.Red)
,
color = MaterialTheme.colors.onPrimary
)
}
}
}
Note: The explicit order helps you to reason about how different
modifiers will interact. Compare this to the view-based system where
you had to learn the box model, that margins applied "outside" the
element but padding "inside" it, and a background element would be
sized accordingly. The modifier design makes this kind of behavior
explicit and predictable, and gives you more control to achieve the
exact behavior you want.
Modifiers documentation:
Use
Box(
modifier = Modifier
.background(MaterialTheme.colors.primary,
RoundedCornerShape(50))
.padding(32.dp)
)
You have to apply the padding modifier after the background.
It is important the order of modifiers.

Avoid one frame delay in calculating child size

I have to create a reusable component that tries to achieve this goal: I have a column that can have content that's larger than the screen height. On the bottom of the screen we have panel with gradient background that can contain button or something else (it's basically a slot in the component). This bottom panel have to be always visible on the screen, and in case of the column being bigger than screen - bottom panel have to be on the top of this column. Gradient background does a nice UX effect so user knows what is going on. It looks like that:
I have that solved, but here's the challenge. The column content have to be scrollable to be on top of the bottom panel when scrolled to the end. Current solution I have is to add a spacer on the bottom of this column. This spacer have the calculated height of the bottom parent. And here's the issue - right now we have calculation done in onSizeChanged which basically results in additional frame needed for the spacer to have correct size.
We did not observe any negative impact of that performance or UX wise. The spacer height calculation never does anything that user can see, but I still want to solve that properly.
AFAIK this can be done using custom Layout, but that seems a little bit excessive for what I want to achieve. Is there another way to do this properly?
Current solution:
#Composable
fun FloatingPanelColumn(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier,
contentHorizontalAlignment: Alignment.Horizontal = Alignment.Start,
bottomPanelContent: #Composable ColumnScope.() -> Unit,
content: #Composable ColumnScope.() -> Unit
) {
val scrollState = rememberScrollState()
var contentSize by remember {
mutableStateOf(1)
}
Box(modifier) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollState)
.then(contentModifier),
horizontalAlignment = contentHorizontalAlignment,
) {
content()
val contentSizeInDp = with(LocalDensity.current) { contentSize.toDp() }
Spacer(modifier = Modifier.height(contentSizeInDp))
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.onSizeChanged {
contentSize = it.height
}
.wrapContentHeight()
.align(Alignment.BottomStart)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0x00FAFCFF),
Color(0xFFF6F9FB),
)
)
),
content = bottomPanelContent
)
}
}
The best way to depend on an other view size during layout is using SubcomposeLayout:
SubcomposeLayout { constraints ->
// subcompose the view you need to measure first
val bottomPanel = subcompose("bottomPanel") {
Column(
// ...
)
}[0].measure(constraints)
// use calculated value in next views layout, like bottomPanel.height
val mainList = subcompose("mainList") {
LazyColumn(
contentPadding = PaddingValues(bottom = bottomPanel.height.toDp())
) {
// ...
}
}[0].measure(constraints)
layout(mainList.width, mainList.height) {
mainList.place(0, 0)
bottomPanel.place(
(mainList.width - bottomPanel.width) / 2,
mainList.height - bottomPanel.height
)
}
}

Resources