Is there something like ScrollView fillViewPort in Jetpack Compose Column?
See this example:
#Composable
fun FillViewPortIssue() {
Column(
Modifier
.fillMaxSize()
.padding(16.dp)
) {
for (i in 0..5) {
Box(
modifier = Modifier
.padding(vertical = 8.dp)
.background(Color.Red)
.fillMaxWidth()
.height(72.dp)
)
}
Spacer(modifier = Modifier.weight(1f))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { /*TODO*/ }
) {
Text("Ok")
}
}
}
This is the result:
When the device is in landscape, the content is cropped, because there's no scroll.
If I add the verticalScroll modifier do the Column...
...
Column(
Modifier
.verticalScroll(rememberScrollState()) // <<-- this
.fillMaxSize()
.padding(16.dp)
) {
...
... the scroll problem is fixed, but the button goes up, like this.
In the traditional toolkit, we can fix this using ScrollView + fillViewPort property. Is there something equivalent to Compose?
Just change the order of the modifiers worked...
Thanks to Abhishek Dewan (from Kotlin Slack channel)!
Column(
Modifier
.fillMaxSize() // first, set the max size
.verticalScroll(rememberScrollState()) // then set the scroll
) {
I had a similar issue recently and substituted verticalScroll for scrollable(rememberScrollState(), Orientation.Vertical) which seemed to work for my case and allow the screen to fill its viewport while allowing scrolling
Related
I am trying to create a Composable that wraps another content Composable and displays a CircularProgressBar as an overlay on top of it, covering the whole content Composable.
I almost got it working as wished, see the following to images:
Initial state
Loading state
But as you can see, the overlay fills the complete screen instead of just the gray LazyRow item and the text field. Thus, the button is pushed off screen.
This is my current code:
#Composable
fun LoadingBox(
modifier: Modifier = Modifier,
isLoading: Boolean = false,
loadingText: String,
content: #Composable() () -> Unit
) {
Box(modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
) {
content()
if (isLoading) {
Surface(
modifier = Modifier
.fillMaxSize()
.alpha(0.5f),
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
Text(text = loadingText)
}
}
}
}
}
On the screenshots, I am providing the gray box and the text field as the content parameter. So the overlay should only cover the gray LazyRow item and the text field.
I already stumbled across instrinsic measures, however I cannot use them as the App crashes when I provide a LazyRow as content due to following error:
java.lang.IllegalStateException: Asking for intrinsic measurements of SubcomposeLayout layouts is not supported. This includes components that are built on top of SubcomposeLayout, such as lazy lists, BoxWithConstraints, TabRow, etc. To mitigate this:
- if intrinsic measurements are used to achieve 'match parent' sizing,, consider replacing the parent of the component with a custom layout which controls the order in which children are measured, making intrinsic measurement not needed
You should:
add contentAlignment = Alignment.Center in the parent Box
Remove the Surface
remove the verticalArrangement in the Column
Add an other Box which you can fill with a translucent background
Something like:
Box(modifier = modifier
.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
content()
if (isLoading) {
Box(
Modifier
.matchParentSize()
.background(Color.Gray.copy(alpha = 0.5f))
)
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
androidx.compose.material.CircularProgressIndicator()
Text(text = loadingText)
}
}
}
I am using two Spacer to give space between view and give padding to bottom item. Now I want to combine both Spacer, but unable to understand how can I give in order. Can anyone guide me how can I achieve that.
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.padding(20.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Text()
Spacer(modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.height(20.dp))
Button()
}
Visual effect
Thanks
If you want to give a vertical padding to the Button just apply Modifier.padding(top=20.dp) in the Button removing the 2nd Spacer.
No reason to use a Spacer with height(20.dp) when the 1st Spacer just covers all the space between the Text and the Button.
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
.padding(20.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Text("text")
Spacer(modifier = Modifier.weight(1f))
//Spacer(modifier = Modifier.height(20.dp))
Button(onClick = {}, modifier=Modifier.padding(top=20.dp)){
Text("Button")
}
}
I have create a Column having 5 Rows, Column has vertical scrolling enabled and Rows has horizontal scrolling enabled.
Sometimes when I try to fling or scroll vertically, Rows consume the gesture and stops vertical scroll to happen.
is there a way to enable horizontal scroll only when certain x-delta is dragged/swipped/scrolled?
code to reproduce :
val colors = remember {
listOf(
Color.Blue,
Color.Green,
Color.Cyan,
Color.Magenta
)
}
Box(
modifier = Modifier
) {
Column(
modifier = Modifier
.padding(top = 200.dp)
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
repeat(5) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.horizontalScroll(rememberScrollState())
) {
repeat(5) {
Box(
modifier = Modifier
.size(200.dp)
.background(
color = remember {
colors.random()
}
)
)
}
}
}
}
}
Edit 1: this is a bug in compose issue added to tracker
I am currently trying to implement a gridview, it consists of 2 columns. I know i can implement my own grid view using columns and rows, but i just want to use existing approach although it is experimental.
#Composable
fun MyGridScreen() {
LazyVerticalGrid(cells = GridCells.Fixed(2), modifier = Modifier.fillMaxSize(),contentPadding = PaddingValues(12.dp)) {
items(15) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(10.dp)
.height(80.dp)
.background(Color.Red)
.aspectRatio(1f)
) {
Text("Grid item $it", color = Color.White)
}
}
}
}
Below is the result i achieved. I can't put space below the item :(
You need to set verticalArrangement and horizontalArrangement properties on the LazyVerticalGrid composable.
This will space the items by 10.dp in both the vertical and horizontal axis.
#Composable
fun MyGridScreen() {
LazyVerticalGrid(
cells = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp
) {
items(15) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(10.dp)
.height(80.dp)
.background(Color.Red)
.aspectRatio(1f)
) {
Text("Grid item $it", color = Color.White)
}
}
}
}
Padding is exactly what you need in this case. But you need to understand that in compose modifiers order means a lot. By moving background modifier in between padding and sizes modifiers, you'll get what you need. And clickable ripple will work the same.
Check out more how modifiers order works: https://stackoverflow.com/a/65698101/3585796
.padding(10.dp)
.background(Color.Red)
.height(80.dp)
.aspectRatio(1f)
p.s. if you need your items to be a square, just put your box in one more box. Looks like width of items in LazyVerticalGrid gets overridden by the grid to fill max width, not sure if that's a bug or a feature.
I'd like to test a simple layout in compose:
A ConstraintLayout (yellow) wrapping
StickyTopText (green)
a Scrolling View (gray)
StickyBottomText (yellow)
I implemented it like this:
#Composable
#Preview
fun MapOverlay() {
ConstraintLayout(
modifier = Modifier
.background(Color.Yellow)
.fillMaxHeight()
) {
val (stickyTop, scroller, stickyBottom) = createRefs()
Text(text = "Sticky Top Text",
modifier = Modifier
.constrainAs(stickyTop) {
top.linkTo(parent.top)
}
.background(Color.Green)
)
Column(
modifier = Modifier
.constrainAs(scroller) {
top.linkTo(stickyTop.bottom)
bottom.linkTo(stickyBottom.top)
height = Dimension.fillToConstraints
}
.verticalScroll(rememberScrollState())
) {
repeat(80) {
Text(
text = "This is Test $it of 80",
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
)
}
}
Text(text = "Sticky Bottom Text",
modifier = Modifier
.background(Color.Red)
.constrainAs(stickyBottom) {
bottom.linkTo(parent.bottom)
})
}
}
Most of it works pretty fine, except the list getting cut off at the end at item 77 instead of 80: (79; zero-Indexed)
What am i doing wrong? Or is this a bug?
(I know i might do this via a scaffold, but that seemed over engineered. Also i would like to understand the issue, not circumvent it
Compose version 1.0.0-beta09
I checked your code and it's really not working on 1.0.0-alpha07, but it's working on 1.0.0-alpha08 (and compose 1.0.0-beta09) 😉
Also, is there any particular reason you're using ConstraintLayout? You can achieve the same result using this code:
Column(
Modifier
.background(Color.Yellow)
.fillMaxHeight()
) {
Text("Sticky Top Text", Modifier.background(Color.Green))
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
) {
repeat(80) {
Text(
"This is Test ${it + 1} of 80",
Modifier
.fillMaxWidth()
.background(Color.LightGray)
)
}
}
Text("Sticky Bottom Text", Modifier.background(Color.Red))
}