Jetpack Compose - Scrollable Column and clip to padding - android-jetpack-compose

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),
) {
...
}

Related

Jetpack compose: how to set intrinsic height as minimum, but expand to fill space?

I'm trying to set it so that a TextField refuses to shrink past its intrinsic height (defined by minLines), but also grow to fill max height:
// context: this is inside of a Column that has fillMaxHeight on it
OutlinedTextField(
// unrelated fields
minLines = 26,
modifier = Modifier
//.height(IntrinsicSize.Min)
.fillMaxWidth()
.fillMaxHeight()
.weight(1f)
.height(IntrinsicSize.Max)
)
However, everything I've tried (setting fillMaxHeight, weight(1f), using requiredHeight instead of height, etc) has issues. It seems like it's mutually exclusive: I can either set a minimum height based on intrinsic height or I can grow to fill the height (using weight seems to work for that, but not fillMaxHeight for some reason.
What is the proper way to have IntrinsicSize be the minimum, but grow to fill remaining space?
If i understand this question correctly you want your TextField height to be one line tall then increases as user enters text. You can selectively change modifier using Modifier.then()
Edit
As of jetpack compose 1.4.0-alpha02 minLines param is added to OutlineTextField and other text composables
#Composable
private fun Sample() {
var text by remember {
mutableStateOf("Hello World")
}
var focused by remember {
mutableStateOf(false)
}
Column(modifier = Modifier.fillMaxHeight()) {
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
minLines = 26,
// unrelated fields
modifier = Modifier
.fillMaxWidth()
.then(
if (focused) {
Modifier.wrapContentHeight()
} else Modifier.fillMaxHeight()
)
.onFocusChanged {
focused = it.isFocused
}
)
}
}

how to set gravity in jetpack compose Text

I have a text component that height is 48dp and want to set the text center in vertical.
Like the TextView with 48dp height and gravity is center_vertical.
Or I have to put it(Text compose) in Surface component?
Yes, that is correct, you would need to put it in a box for instance, or a column, and center it that way.
Box(modifier = Modifier.fillMaxSize, contentAlignment = Alignment.Center){
Text(text = "My Text") }

BottomBar overlays scaffold content when using LazyColumn

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
}
}

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