Layout not moving up when keyboard is open in Jetpack Compose - android

I have a layout in Jetpack Compose where I have a couple of composables (text view and input) that I need to stay at the top of the screen, and then another composable (button) that I need to stay attached to the bottom of the screen. When the keyboard is opened to type into the text input, I need the button attached to the bottom of the screen to move up with it, however this isn't happening.
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.padding(0.dp, 32.dp, 0.dp, 0.dp)
.navigationBarsWithImePadding()
.verticalScroll(rememberScrollState()),
) {
Column {
CustomHeading6(
modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 32.dp),
title = "Enter Email"
)
CustomBody1(
modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 12.dp),
content = "Email:"
)
CustomEmailTextField(
modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 20.dp),
text = viewModel.emailAddress,
onValueChanged = { //removed }
)
}
Column {
CustomButton(
modifier = Modifier.padding(0.dp, 32.dp),
title = "Submit",
onButtonClick = { //removed }
)
}
}
}
I tried to achieve the attach to top/bottom by wrapping each set of items in their own columns and then using Arrangement.SpaceBetween on the wrapper column, but perhaps this isn't correct. I also tried without this and instead adding .weight(1f) to the column with the composables attached to the top. Neither of these allowed the layout to move up with the keyboard.
I also have this in the main activity:
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ProvideWindowInsets {
Theme {
// my content
}
}
}
and this in the manifest for my activity:
android:windowSoftInputMode="adjustResize"

Related

When adding lazy column inside another lazy column in android jetpack compose it shows error

How to add a lazy column inside another lazy column in jetpack compose like below screenshot.(The inner list size maybe vary)
#Composable
fun SingleItem() {
LazyColumn(modifier = Modifier
.fillMaxWidth()
),
) {
item {
Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_school),
contentScale = ContentScale.Crop,
contentDescription = null
)
Text(
text = "School",
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
Icon(
painter = painterResource(id = R.drawable.ic_down_arrow),
modifier = Modifier
.size(dimensionResource(id = R.dimen.margin_large))
.clip(CircleShape)
.clickable { isExpand = !isExpand },
contentDescription = stringResource(id = R.string.expand_list)
)
}
}
if (isExpand) {
item {
Divider(
modifier = Modifier
.padding(vertical = dimensionResource(id = R.dimen.margin_large))
.background(DividerColor.copy(alpha = 0.5F))
.fillMaxWidth(), thickness = 0.5.dp
)
}
items(3) {
Row(modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.margin_large))
.fillMaxWidth()) {
Text("School",
modifier = Modifier
.fillMaxWidth()
.weight(1f),
)
Text(text = "3 KM",
textAlign = TextAlign.End
)
}
}
}
}
}
//For Full list
#Composable
fun MainList()
{
LazyColumn() {
items(10) {
SingleItem()
}
}
}
But using this code it shows following error.
Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.
It is recommended to avoid Nested Scrolling in the same direction. One possible hack if you already know the size of the component you can use that in modifier.
or try wrapping all of your composables inside one parent LazyColumn and using its DSL to pass in different types of content.
In your example
#Composable
fun MainList()
{
LazyColumn() {
items(10) { item ->
ListComposable(item)
}
}
}
#Composable
private fun ListComposable(item: List<String>){
//...
}
This item can also be a multiple list item.
Reference: https://developer.android.com/jetpack/compose/lists#avoid-nesting-scrollable

Jetpack Compose: How to modify the width of Drawer in ModalNavigationDrawer?

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
}

Koltin Jetpack compose, ModalBottomSheetLayout stop in the middle of the screen

I'am new to jetpack compose and i really liked it. But ran into a problem : I to have a modal that take all my screen size, following multiples tutorial i was able to do it using ModalBottomSheetLayout, Here is my code :
#ExperimentalMaterialApi
#Composable
fun DetailsScreen() {
val coroutineScope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
ModalBottomSheetLayout(
sheetContent = {
Box(
modifier = Modifier
.navigationBarsWithImePadding()
.fillMaxSize()
.wrapContentHeight() //changed here to have full screen size, but it was only half screen, i had to slide with my finger to make the modal to full screen
//.height(200.dp) initial value i have a little modal on the bottom
//.fillMaxWidth()
.background(Color.White)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
) {
Text(
text = "Bottom Sheet",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold,
modifier = Modifier.fillMaxWidth()
)
Text(
text = "Item 1",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 10.dp)
)
Text(
text = "Item 2",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 10.dp)
)
Text(
text = "Item 3",
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(top = 10.dp)
)
}
}
},
sheetState = sheetState,
sheetBackgroundColor = Color.White
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = "Modal Bottom Sheet",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
)
}
) {
MainContent(
onClick = {
coroutineScope.launch {
//sheetState.show() // changed here make the modal full screen at the beginning
sheetState.animateTo(ModalBottomSheetValue.Expanded)
}
},
modifier = Modifier.padding(it)
)
}
}
}
#Composable
fun MainContent(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.fillMaxSize()
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(10.dp)
) {
Text(
text = "Click Me!",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6.copy(color = Color.White)
)
}
}
}
Problem i encounter : at first the modal was just at the bottom so i made my box to fillMaxSize to make it full screen, but it only open to half screen and i slide to swipe up to make it full screen.
Then i found that i could change sheetState.show() to sheetState.animateTo(ModalBottomSheetValue.Expanded) to make my modal full screen from the moment it display. Perfect it's what i wanted. here is a screen of the modal :
The problem is that now when i slide down to dismiss the modal it's stop half scree as bellow:
From there i have to swipe down an other time to make it disappear. Also when it's on the middle i can swipe up to make it back to full screen. It's seem that there is 3 states: fullscreen, halfscreen, and gone. And the modal can be full screen or halfscreen. I would like to remove the half screen. So when i open the modal it's fullscreen and when i swipe down to remove it, it disappear without stoping in the middle. So i would have only one gesture to remove it.
I think you can get this behavior with skipHalfExpanded property:
val bottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Expanded, skipHalfExpanded = true)

How do I create a Jetpack Compose Column where a middle child is scrollable but all of the other children are always visible?

I am creating a Jetpack Compose Dialog that contains a Column where all of the elements should always be visible, except for the third element, which is a Text that should be scrollable if the text doesn't fit the screen space. I almost achieved this with a secondary scrollable Column just for that Text element. However, this implementation pushes the bottom child (a button) out of view if there is a lot of text. Here is my code:
#Composable
fun WelcomeView(
viewModel: WelcomeViewModel,
onDismiss: () -> Unit
) {
Dialog(onDismissRequest = onDismiss) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(Spacing.extraLarge))
.background(Colors.backgroundBase)
.padding(all = Spacing.medium)
) {
Column {
IconView(
name = IconViewNames.RUNNING_SHOE,
size = IconViewSizes.LARGE,
color = Colors.primaryBase
)
Text(
viewModel.title,
style = Text.themeBillboard,
modifier = Modifier.padding(bottom = Spacing.medium)
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Text(
viewModel.message,
style = Text.themeHeadline,
modifier = Modifier.padding(bottom = Spacing.medium)
)
}
Button(
onClick = onDismiss,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(Spacing.medium))
.background(Colors.primaryBase)
) {
Text(
"Continue",
style = Text.themeHeadline.copy(color = textExtraLight),
modifier = Modifier.padding(all = Spacing.extraSmall)
)
}
}
}
}
}
#Preview
#Composable
fun PreviewWelcomeView() {
WelcomeView(
viewModel = WelcomeViewModel(
firstName = "Wendy",
htmlWelcomeMessage = PreviewTextFixtures.threeParagraphs
),
onDismiss = {}
)
}
This is what it (correctly) looks like when there is only one paragraph of text:
But this is what it looks like when there are three paragraphs. The text scrolls correctly, but notice the missing "Continue" button:
Use this for your middle (scrollable) composable
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.weight(weight =1f, fill = false)
) {
Text("Your text here")
}
The key is to use fill = false.
Just apply to the scrollable Column the weight modifier.
Something like:
Column (verticalArrangement= Arrangement.SpaceBetween) {
Text(
"viewModel.title",
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.weight(1f, false)
) {
Text("...")
}
Button()
}

Provide different alignments to composables within 'Column'

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

Categories

Resources