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)
}
}
}
Related
I want to display a sticky bar at the bottom of the screen to show dynamic or static data similar to the bars in the image below. Can this be achieved with BottomSheetScaffold on screen load without user interaction? If not, how else can this be implemented?
EDIT: I want to achieve this in Jetpack Compose.
This UI collection might be a good starting point for you to see which elements/classes you need. It looks like you're depicting a BottomSheet element? Maybe an AndroidSweetSheet element - Both of these look like they'd suit your purpose. You don't need the animations or can turn off the resizable, if you want them static. I'd start there with that huge collection of curated UI elements/widgets anyway, see if those two, or perhaps some other of the many UI element better fits your purpose.
For example, this is the AndroidSweetSheet: (1)
And heres the BottomSheet (2):
Let the page load (lots of example images so may take a little while depending on your connection/CPU) but theres several that look like what you want. May have extras that you can turn off, such as the resizable nature of that AndroidSweetSheet, you can have it appear rather than slide up & scroll
Just having a look through that UI elements page (for my own app purposes) I noticed this one 3 which might suit your purposes - imagine having a star pop up instead of the play icon, and when the user presses the star, it reveals your "Go premium" panel instead of the music player widget:
OP commented that they're specifically looking for jetpack compose - what about this bottom-sheet?
Although this example looks very bare-bones, the structure looks pretty close to what you're describing in yout text/images:
You'd probably have the pop-up controlled so that it pops-up when you want it to, instead of the user clicking a button.
Edits: some more examples
This code tutorial also suggests building a bottom sheet using jetpack compose.
I'd think you'd probably want your "go premium" box to be modal, and this page does just that, again with jetpack compose - looks prettier, too!
You can consider this one where you can utilize LaunchedEffect with a Unit key to show the bottom sheet immediately on first composition, and closing it using an Icon placed at the end of the gray "header".
All these codes are copy-and-paste-able so you can run it with no issues.
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun MyScreenAutoShowBottomSheet() {
val contentBackground = Color(0xFFa593b8)
val sheetPeekHeight = 40.dp
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
LaunchedEffect(key1 = Unit){
bottomSheetScaffoldState.bottomSheetState.expand()
}
val coroutineScope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetBackgroundColor = contentBackground,
sheetElevation = 0.dp,
sheetPeekHeight = sheetPeekHeight,
sheetContent = {
Column(
modifier = Modifier
.padding(top = sheetPeekHeight)
.wrapContentHeight()
.fillMaxWidth()
.background(Color.DarkGray)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
modifier = Modifier.padding(8.dp),
text = "Go Premium"
)
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.End
) {
Icon(
modifier = Modifier
.clickable {
coroutineScope.launch {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
},
imageVector = Icons.Default.Add,
contentDescription = null
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color(0xFF4fc992))
)
}
},
floatingActionButton = {
FloatingActionButton(
backgroundColor = Color(0xFF4a6ebd),
shape = CircleShape,
onClick = {},
) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "icon")
}
}
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(contentBackground)
)
}
}
A simpler solution without the use of sheets is to use a Box composable with the last element having a Modifier.align(Alignment.BottomCenter) modifier:
Box(
modifier = Modifier.fillMaxSize()
) {
//Main screen contents
Column() {...}
...
Row (
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.height(50.dp)
.background(Color(0xFF4fc992))
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
//Bottom bar contents
}
}
I'm creating a component in Jetpack Compose and realized that when I'm making a Composable inside a Box it's possible that this component assumes 2 maximum fill possibilities: Modifier.fillMaxSize() and Modifier.matchParentSize(). As shown below:
Box(
modifier = modifier // This modifier is received by parameter of another composable function
) {
Canvas(modifier = Modifier.matchParentSize()) {
// Using match parent size
}
}
and
Box(
modifier = modifier // This modifier is received by parameter of another composable function
) {
Canvas(modifier = Modifier.fillMaxSize()) {
// Using fill max size
}
}
What is the practical difference between these 2 modes? And why can't I set Modifier.matchParentSize() to a Column or a Row?
From official doc
Modifier.fillMaxSize modifier, which makes an element occupy all available space, will take part in defining the size of the Box.
So it specifies the size of the element.
But if you use Modifier.matchParentSize() in an element inside of a box it has nothing to do with specifying the size of the box.
The size of the box will be measured by other children element of the box. Then the element with the Modifier.matchParentSize() will match and occupy that size.
You can't use .matchParentSize() in Row or Column because this modifier is part of the BoxScope interface. So it works only with boxscope.
For example, if you use fillMaxSize with something like the below.
Box() {
Text(
modifier = Modifier
.fillMaxSize()
.background(Color.Green),
text = ""
)
Text(
modifier = Modifier
.size(100.dp)
.background(Color.Blue),
text = "Hello",
)
}
You will get this. It will fill the entire screen because of that .fillMaxSize() modifier in the first child.
But if you use this
Box() {
Text(
modifier = Modifier
.matchParentSize()
.background(Green),
text = ""
)
Text(
modifier = Modifier
.size(100.dp),
text = "Hello",
)
}
It will take only 100.dp for the Hello text and then the green background will fill that 100.dp because of that .matchParentSize() modifier in the first child.
I could have used Box instead of Text but more Box can make it confused.
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.
According to https://google.github.io/accompanist/insets/ we can use a nested scroll connection to control the keyboard appearance when scrolling.
The sample code has the modern impl for a LazyColumn:
LazyColumn(
reverseLayout = true,
modifier = Modifier
.weight(1f)
.nestedScroll(connection = rememberImeNestedScrollConnection())
) {
items(listItems) { imageUrl -> ListItem(imageUrl, Modifier.fillMaxWidth())
}
}
The doc has a sample for ScrollableColumn which is no longer a thing as of beta06:
// Here we're using ScrollableColumn, but it also works with LazyColumn, etc.
ScrollableColumn(
// We use the nestedScroll modifier, passing in the
// the connection from rememberImeNestedScrollConnection()
modifier = Modifier.nestedScroll(
connection = rememberImeNestedScrollConnection()
)
) {
// list content
}
What is the equivalent for Modifier.verticalScroll()? I've tried combining it with nestedScroll() to no effect.
val imeNestedScrollConnection = rememberImeNestedScrollConnection()
Column(
modifier = modifier
.fillMaxHeight()
.fillMaxWidth()
.verticalScroll(scrollState)
.nestedScroll(imeNestedScrollConnection)
.padding(horizontal = spacing, vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) { ... }
Other things I've tried
Changing the order of the two scroll modifiers (no difference)
Having only the nestedScroll modifier (column no longer scrolls at all)
I have a 'Text' and another composable within a Column. I want to put the Text to the top and the layout defined by the composable to the bottom. I am not able to find a way to provide different alignments to the 'elements' within Column, as the alignment/arrangement specified in the 'Modifier' of Column works for all the 'elements' within it. My current code aligns both to the bottom. This is my code:
#Composable
fun SignUpDetails(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "First Screen\n" +
"Click me to go to Second Screen",
color = Color.Green,
style = TextStyle(textAlign = TextAlign.Center),
modifier = Modifier
.padding(24.dp)
.clickable(onClick = {
// this will navigate to second screen
navController.navigate("second_screen")
})
)
ProgressBar1UI()
}
}
How do I achieve my requirement?
You can use the verticalArrangement = Arrangement.SpaceBetween:
Place children such that they are spaced evenly across the main axis, without free space before the first child or after the last child
Something like:
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween
) {
Text( /*...*/ )
ProgressBar1UI()
}
Otherwise you can use something different like a Box and the Modifier.align:
Box(modifier = Modifier.fillMaxSize()) {
Text(
modifier = Modifier
.align(Alignment.TopCenter)
)
ProgressBar1UI(Modifier.align(Alignment.BottomCenter))
}
The other way you can achieve such behavior (one view top second view bottom) is to add the Spacer component with a weight modifier between items.
#Composable
fun Example() {
Column {
Text("FirstText")
Spacer(modifier = Modifier.weight(1f))
Text("SecondText")
}
}