Is there a way to set height of elements in a Scaffold to be exactly the same? I want both the top bar and the content to occupy half of the screen's height
Scaffold(
topBar = {
MyLargeTopAppBar(modifier = Modifier.height(1f))
},
content = {
LazyColumn(modifier = Modifier.padding(it).height(1f)) {
...
}
},
containerColor = MaterialTheme.colorScheme.background
)
Arthur Kasparian's suggestion
You can skip using a scaffold if none of its other elements are needed, the layout then simply becomes a column like so:
Column(
modifier = Modifier.fillMaxSize()
) {
TopAppBar(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {}
Content(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
}
The height of the content body is determined in part by the topBar body height, and the weight setter is inaccessible in those scopes, so I don't think it's possible. However what you can do is simply include the top bar in the content:
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val size = screenHeight / 2
Scaffold(
content = {
Column {
TopAppBar(
modifier = Modifier
.height(size)
.weight(1f)
.fillMaxWidth()
) {
Text("$size")
}
Box(
modifier = Modifier
.height(size)
.weight(1f)
.fillMaxWidth()
.background(Color.Red)
) {
Text("$size")
}
}
},
)
You get something like this:
Related
Recently, I try to migrate to Jetpack compose.
So, I want to show 'card' where has 'indicator' while loading
but I can't change 'indicator' size in 'SubcomposeAsyncImage'
#Composable
private fun SponsorDialogContent(
imgUrl: String,
) {
Card(
modifier = Modifier
.width(300.dp)
.wrapContentHeight(),
elevation = CardDefaults.cardElevation(0.dp),
shape = RoundedCornerShape(10.dp)
) {
Box() {
SubcomposeAsyncImage(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.heightIn(min = 200.dp),
model = imgUrl,
contentDescription = null,
loading = {
CircularProgressIndicator(modifier = Modifier.size(30.dp).align(Alignment.Center), progress = 1f)
},
alignment = Alignment.Center,
contentScale = ContentScale.FillWidth
)
CircularProgressIndicator(1f, Modifier.width(30.dp).align(Alignment.Center))
}
}
}
So. I have try the code below
#Composable
private fun SponsorDialogContent(
imgUrl: String,
) {
Card(
modifier = Modifier
.width(300.dp)
.wrapContentHeight(),
elevation = CardDefaults.cardElevation(0.dp),
shape = RoundedCornerShape(10.dp)
) {
Box() {
SubcomposeAsyncImage(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.heightIn(min = 200.dp),
model = imgUrl,
contentDescription = null,
loading = {
Box(contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
},
alignment = Alignment.Center,
contentScale = ContentScale.FillWidth
)
}
}
}
Then, works well, but I don't know why
Does anyone know why?
I tried to find how to work this code but failed
As #Jan Bina mentioned it has propagateMinConstraints = true which means it forces minimum constraints to its direct child only. I explained in this answer how it effects with Surface because Surface is a Box with propagateMinConstraints = true. You can try out these with Box and you will see the same outcomes
Surface(
modifier = Modifier.size(200.dp),
onClick = {}) {
Column(
modifier = Modifier
.size(50.dp)
.background(Color.Red, RoundedCornerShape(6.dp))
) {}
}
Spacer(modifier = Modifier.height(20.dp))
Surface(
modifier = Modifier.size(200.dp),
onClick = {}) {
Column(
modifier = Modifier
.size(50.dp)
.background(Color.Red, RoundedCornerShape(6.dp))
) {
Column(
modifier = Modifier
.size(50.dp)
.background(Color.Green, RoundedCornerShape(6.dp))
) {}
}
}
Spacer(modifier = Modifier.height(20.dp))
Box(
modifier = Modifier.size(200.dp)
) {
Column(
modifier = Modifier
.size(50.dp)
.background(Color.Red, RoundedCornerShape(6.dp))
) {
Column(
modifier = Modifier
.size(50.dp)
.background(Color.Green, RoundedCornerShape(6.dp))
) {}
}
}
In first example on Surface forces Column to have 200.dp size even though it has Modifier.size(50.dp).
In second example Box inside Column has 50.dp size because it's not a direct descendant of Surface.
In third example if we replace Surface(Box with propagateMinConstraints true) with Box it allows direct descendant to use its own constraints or dimensions.
And when you set Modifier.fillMaxWidth you set Constraints as minWidth = parent width, maxWidth = parentWidth
I answered about Constraint types here
If you browse the source code of SubcomposeAsyncImage, you will get to the point where you can see this:
BoxWithConstraints(
modifier = modifier, // this is the modifier you passed to SubcomposeAsyncImage
contentAlignment = alignment,
propagateMinConstraints = true,
) {
// image or your loading content based on state
}
So the behavior you describe is because of your modifier that has heightIn(min = 200.dp) and your progress indicator that ends up being direct child of this Box which has propagateMinConstraints = true.
Recently I've been trying Material3 combined with Jetpack Compose, and I need a drawer along with a scaffold in one of my screen. It's sad that Scaffold in material3 hasn't support drawer yet, but luckily ModalNavigationDrawer can be found as a substitute. However, using ModalNavigationDrawer, the drawer's content is always covering the whole screen when drawer is opened, and I can't find any parameter to set it's width to a proper value, e.g. half of the screen width. Is there any way I can solve this problem?
My compose version is 1.2.0-beta02 and my material3 version is 1.0.0-alpha12.
Use a ModalDrawerSheet in the drawerContent and then set its modifier to the desired width:
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
modifier = Modifier
.width(300.dp) // or your desired width
.fillMaxHeight()
) {
...Your Drawer Content...
}
}
)
Code sample on the Material3 developer's page
and the ModalDrawerSheet docs
From the source code of ModalNavigationDrawer, the max-width is set in modifier and the drawer content's column is set to fill max size so by default it will take NavigationDrawerTokens.ContainerWidth's value as width.
But a work-around exists here, what you can do is you need to set drawerContainerColor as Color.Transparent and put another column or box inside the drawerContent with your required size.
You can use requiredWidth modifier to fix the required width or you can use sizeIn modifier according to min and max-width.
ModalNavigationDrawer(
drawerContent = {
Column(
modifier = Modifier
.requiredWidth(250.dp)
.fillMaxHeight()
.background(Color.White, RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp))
) {
//Drawer Content
}
},
drawerContainerColor = Color.Transparent
) {
//Main Content
}
Update with click to close the drawer on tap of the background -
If you want to have the ripple effect on Spacer's clickable just remove the interactionSource and indication parameters.
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Row(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.requiredWidth(250.dp)
.fillMaxHeight()
.background(
Color.White,
RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp)
)
) {
//Drawer Content
}
Spacer(
modifier = Modifier
.fillMaxSize()
.clickable(
interactionSource = MutableInteractionSource(),
indication = null
) {
scope.launch {
if (drawerState.isOpen) {
drawerState.close()
}
}
},
)
}
},
drawerContainerColor = Color.Transparent
) {
//Main Content
}
I have tweaked #Priyank-Jain's answer a little bit to clean up the elevation left behind by the original drawer. Remove the elevation from the original draw and add it to the newly created one
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Row(modifier = Modifier.fillMaxWidth()) {
Surface(
modifier = Modifier
.requiredWidth(320.dp)
.fillMaxHeight(),
color = Color.White,
shape = RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp),
elevation = 16.dp
) {
//Drawer Content
}
Spacer(
modifier = Modifier
.fillMaxSize()
.clickable(
interactionSource = MutableInteractionSource(),
indication = null
) {
scope.launch {
if (drawerState.isOpen) {
drawerState.close()
}
}
},
)
}
},
drawerContainerColor = Color.Transparent,
drawerElevation = 0.dp,
) {
//Main Content
}
I'm trying to overlap two Box or perhaps is better to use Row on this case.
My design is one Row overlapped with another one, and I've wrapped it on a Column, is that correct?
This is the design, what I'd like to have is the rectangle of the top be the same size of the one below and then move it some pixels as you can see in the image, but they should have the same width but not the same height.
Is it okay if the hierarchy is :
Column
Box (the one of the top)
Row
Box (the one of the bottom)
Row (inside there is text and it's all the same align)
......
I've faced with this some days ago and I solved it using ConstraintLayout.
What I had to do is :
Add implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-beta02" to build.gradle
Wrap every Box in a ConstraintLayout { .. }
Inside each Box add a Modifier.constrainAs to align the Top Bottom Start End as you want.
If you want the first box be the same width as the second one without hardcoding the dps you should use width = Dimension.fillToConstraints
fillToConstraints - the layout will expand to fill the space defined by its constraints in that dimension.
Basic example without hard-coding :
ConstraintLayout() {
val (title, description) = createRefs()
Box(
modifier = Modifier
.padding(start = 28.dp)
.background(color = Red)
.padding(
horizontal = 16.dp,
)
.constrainAs(title) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
}
) {
Text(text = "Hello World")
}
Box(
modifier = Modifier
.padding(end = 4.dp)
.background(Color.Magenta)
.padding(bottom = 5.dp, start = 8.dp, end = 16.dp, top = 4.dp)
.constrainAs(description) {
top.linkTo(title.top, margin = 16.dp)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
) {
Text(text = "Skizo-ozᴉʞS rules")
}
}
Now you have to play with the padding according to your UI and adapt it, result is something like this :
This is way using BoxWithConstraints and not using fixed width and height:
BoxWithConstraints(
Modifier
.background(color = Color.Blue)
.padding(20.dp)) {
val boxWidth = this.maxWidth
Box(
modifier = Modifier
.width(boxWidth - 10.dp)
.background(Color.Red)
) {
Text(text = "Hello Android")
}
Column() {
Spacer(modifier = Modifier
.height(10.dp)
.width(10.dp))
Row( ) {
Spacer(modifier = Modifier.width(10.dp))
Box(
modifier = Modifier
.width(boxWidth)
.zIndex(2f)
.background(Color.Yellow)
) {
Text("aa", modifier = Modifier.background(color = Color.Green))
}
}
}
}
In order for the Composables to overlap, you should put them in the same Box. Try this out:
Box(modifier = Modifier.size(width = 300.dp, height = 100.dp)) {
Row(modifier = Modifier
.size(width = 200.dp, height = 50.dp)
.background(color = Color.Blue)
.align(Alignment.TopEnd)) {}
Row(modifier = Modifier
.size(width = 200.dp, height = 70.dp)
.background(color = Color.Red)
.align(Alignment.BottomStart)) {}
}
You can achieve this in many ways,
#Composable
fun BoxOverBox() {
Box(
modifier = Modifier.fillMaxSize()
.background(Color.White),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(200.dp)
.height(50.dp)
.background(Color.Red)
)
Box(
modifier = Modifier
.width(200.dp)
.height(50.dp)
.zIndex(2f)
.graphicsLayer {
translationX = -50f
translationY = 50f
}
.background(Color.Blue)
)
}
}
I think you must use "matchParentSize" modifier that is avaliabele inside BoxScope, I mean inside Box composable, that modifer measure other children size except itself when it has join the composable at first time to apply the same size to itself.
you can see this modifier in documentation
https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/BoxScope#(androidx.compose.ui.Modifier).matchParentSize()
I am looking to have a result like this. Two block with 2/3 size and 1/3 size respectively.
I am getting the expected result with this code.
#Preview(showBackground = true)
#Composable
fun LayoutCheck() {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Surface(
modifier = Modifier
.width(200.dp)
.weight(3f),
color = MaterialTheme.colors.primary
) {}
Surface(
modifier = Modifier
.width(200.dp)
.weight(1f),
color = MaterialTheme.colors.secondary
) {}
}
}
But when i put that inside a lazycolumn, nothing seems working. Not even getting a display.
#Preview(showBackground = true)
#Composable
fun LayoutCheck() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(20.dp)
) {
item {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Surface(
modifier = Modifier
.width(200.dp)
.weight(3f),
color = MaterialTheme.colors.primary
) {}
Surface(
modifier = Modifier
.width(200.dp)
.weight(1f),
color = MaterialTheme.colors.secondary
) {}
}
}
}
}
}
If i remove t he weight and put some height,it works. But i don't want to hardcode there. So how to make it work with weight. Expecting some help..
Thanks
NB: I want scroll functionality, that's why going with LazyColumn
Real World scenario :
A Login Screen, with Logo at the first 2/3 portion and a Text and Button at the bottom 1/3 portion
Scenario 1 : Working Perfectly:
Scenario 2: If users font is bigger or screen is rotated, they wont be able to see the button
Since you have only one item just use a Column with a verticalScroll:
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally,
) {
//...
}
Also if you want 2/3 and 1/3, use in the 1st Surface the weight(2f) modififer.
You prevent the text from being resized by applying the weight modifier. Instead, I suggest that you make the element spacing flexible, like this:
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.my_image),
contentDescription = "",
)
Spacer(Modifier.weight(1.35f))
Text(
"Your app is being reviewed",
)
Spacer(Modifier.weight(0.2f))
Button(onClick = { /*TODO*/ }) {
Text("Log out")
}
Spacer(Modifier.weight(0.2f))
}
Spacers explanation:
I am trying to make a short content center-aligned on the screen inside a Scaffold content. Using fillMaxSize() on the scaffold contents seems like having no impact at all. How can I make this content full size?
Scaffold(
topBar = { },
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = { },
bottomBar = { }
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(paddingValues)
) {
NavHost(
navController = navController,
startDestination = Screen.Running.route
) {
composable(Screen.Business.route) {
BusinessScreen(onSetAppTitle = { appTitle = it })
}
...
}
#Composable
fun BusinessScreen(onSetAppTitle: (String) -> Unit) {
LaunchedEffect(Unit) {
onSetAppTitle("Business Dashboard")
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.padding(16.dp)
) {
Text("Business Dashboard")
}
}
verticalScroll wraps content, and can be stretched to very long. That's why .fillMaxHeight (which is a part of fillMaxSize) doesn't work inside verticalScroll: it's ambiguous.
You need to set height explicitly. This is the case when you can pass it from onSizeChanged modifier added on the top Column.
But I believe that your composition is generally not really good: applying verticalScroll to a view on top of the layout tree is not the best solution.
Try adding verticalScroll inside each route only to those views that are really bigger than the screen.