Change alignment of child in column in jetpack compose - android

I want to change the horizontalAlignment of particular child and remaining will use the same horizontalAlignment. Is this possible in Column?
For example, Column parent modifier use horizontalAlignment = Alignment.CenterHorizontally, and I want particular child modifier will be different horizontalAlignment.
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
ScreenImage()
Description()
if (viewModel.isBluetoothEnabled) {
ScanDeviceList(scanDeviceList)
} else {
Warning()
Spacer(modifier = Modifier.weight(1f))
TryAgainButtonView { tryAgainAction() }
ButtonView { openSettingAction() }
}
}
I want to change ScanDeviceList() to be different horizontalAlignment
#Composable
fun ColumnScope.ScanDeviceList(scanDeviceList: List<ScanResult>) {
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(scanDeviceList.isNotEmpty()) {
Text(
text = stringResource(R.string.vailable_device),
)
}
}
Many Thanks

You can use Modifier.align(Alignment.Start) to align a particular child.
So for example to make your ScanDeviceList() at the Start of the column the code will be like this:
#Composable
fun ColumnScope.ScanDeviceList(scanDeviceList: List<ScanResult>) {
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(
scanDeviceList.isNotEmpty(),
modifier = Modifier.align(Alignment.Start)
) {
Text(
text = stringResource(R.string.vailable_device),
)
}
}
You can also pass the modifier as an argument to ScanDeviceList composable function to make it more generic to the code will be like this:
#Composable
fun ColumnScope.ScanDeviceList(
scanDeviceList: List<ScanResult>,
modifier: Modifier = Modifier
) {
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(
scanDeviceList.isNotEmpty(),
modifier = modifier
) {
Text(
text = stringResource(R.string.vailable_device),
)
}
}
And when you call it you can specify the alignment that you want:
ScanDeviceList(scanDeviceList, modifier = Modifier.align(Alignment.Start))
Note: adding the modifier an argument to your composable functions is considered as a best practice because it will make the function reusable like the example above you can call ScanDeviceList with Alignment.Start or Alignment.End without changing the function itself, just by passing a different modifier as a parameter.

Related

Android compose Modifier width unexpected behaviour inside scrollable/lazy row

I am building a basic layout which diplays a horizontally scrollable row with cards. The cards are a column component with a couple dividers in the middle:
#Composable
fun TestCard(
name: String
) {
Column(
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(12.dp),
horizontalAlignment = Alignment.Start
) {
Text(text = name, style = Typography.h4.bold.black)
Spacer(modifier = Modifier.height(8.dp))
Divider(color = Color.Black) //DOES NOT APPEAR IN ROW
Spacer(modifier = Modifier.height(8.dp))
Divider(modifier = Modifier.fillMaxWidth(), color = Color.Black) //DOES NOT APPEAR IN ROW
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Other text", style = Typography.h4.bold.black)
}
}
I also have a scrollable row that contains a list of these cards:
#Composable
fun TestComponent() {
val scrollState = rememberScrollState()
val names = listOf("Martha", "Erik","Steve","Roy","Pete")
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.primary),
horizontalAlignment = Alignment.Start
) {
Row(Modifier.horizontalScroll(scrollState), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
names.forEach { name ->
TestCard(name)
}
}
}
}
When I display the card on its own, I can see both dividers. When I display the row containing cards the dividers don't appear, because their width is set to 0 (I can verify with LayoutInspector the dividers are there). Visual representation:
I know I can create a state variable for width that would be updated inside the Modifier.onSizeChanged but I am sure there must be a reason for this to happen and a better solution.
The Divider has inside a fillMaxWidth() modifier, but as you check in the doc:
If the incoming maximum width is Constraints.Infinity this modifier will have no effect.
It happens because of the horizontalScroll.
You have to specify the width for the Dividers and the Spacers.
A workaround is to apply width(IntrinsicSize.Max) to the Column in the TestCard. In this way the Constraints have a Constraints.fixedWidth(width):
#Composable
fun TestCard(
name: String
) {
Column(
modifier = Modifier
.width(IntrinsicSize.Max)
.background(androidx.compose.material.MaterialTheme.colors.surface)
.padding(12.dp),
horizontalAlignment = Alignment.Start
) {
Text(text = name,)
Spacer(modifier = Modifier.height(8.dp).fillMaxWidth())
Divider(color = Color.Black, modifier = Modifier.fillMaxWidth())
Spacer(modifier = Modifier.height(8.dp).fillMaxWidth())
Divider(modifier = Modifier.fillMaxWidth(), color = Color.Black)
Spacer(modifier = Modifier.height(8.dp).fillMaxWidth())
Text(text = "Other text" )
}
}

Android Compose, clickable surface on top of buttons

Is it possible to create full screen, clickable, transparent surface/box, that will overlap compose buttons and other composables. I want to keep buttons visible but unreachable for the user.
When I set them(buttons) as disabled, they turn to an unclickable area( on top of the clickable surface) that I can't have on the screen. Other composables, like Text,Box etc. act like they are "under" clickable surface.
#Composable
fun ShowAnswers(question: Question, onSurfaceClick: () -> Unit, questionsLeft: Int) {
Surface(modifier = Modifier.clickable { onSurfaceClick() }) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(8.dp)
) {
Text(
text = stringResource(id = R.string.questions_left, questionsLeft),
fontSize = 24.sp
)
Spacer(modifier = Modifier.height(20.dp))
QuestionCard(question = question)
Spacer(modifier = Modifier.height(20.dp))
ShowCorrectAnswer(question = question)
}
}
}
ShowCorrectAnswer(...) contains buttons that i need to "overlap"
You can use a simple transparent Box to overlay the Surface with the question.
Something like:
var isActive by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxSize()) {
Surface() {
Column() {
//....
QuestionCard(question = question)
//....
Button(onClick = { isActive = true }) {
//...
}
}
}
if (isActive){
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Transparent)
.clickable(
enabled=isActive,
interactionSource = interactionSource,
indication = null)
{
//do something
}
)
}
}
Adding a fillMaxSize Box inside the surface, after column, solved the problem.
But placing the Box before Column makie it appear "under" it.
#Composable
fun ShowAnswers(question: Question, onSurfaceClick: () -> Unit, questionsLeft: Int) {
Surface {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(8.dp)
) {
Text(
text = stringResource(id = R.string.questions_left, questionsLeft),
fontSize = 24.sp
)
Spacer(modifier = Modifier.height(20.dp))
QuestionCard(question = question)
Spacer(modifier = Modifier.height(20.dp))
ShowCorrectAnswer(question = question)
}
**Box(modifier = Modifier
.fillMaxSize()
.background(Color.Transparent)
.clickable { onSurfaceClick() })**
}
}

How to fillMaxSize() the Box() inside row - Compose

I want to align the button in the BottomEnd of the Box in Row (In the BottonEnd of the bellow Card). I have Card with Row who is devided in two parts - Card and Box, and I want the Box to fill max of the rest of the row. I cannot implement it how I would wanted. Bellow I attached the visualization of the current code.
.
#Composable
fun ProductItem(product: ProductModel, onItemClick: () -> Unit, onAddToCardButton: () -> Unit) {
Card(
modifier = Modifier
.fillMaxSize()
.padding(7.dp)
.clickable { onItemClick() },
shape = MaterialTheme.shapes.large,
elevation = 4.dp
) {
Row(
modifier = Modifier.fillMaxSize()
) {
Card(
modifier = Modifier
.weight(1f),
shape = MaterialTheme.shapes.small,
elevation = 2.dp
) {
Image(
painter = painterResource(id = R.drawable.ic_splash_screen),
contentDescription = "Image of ${product.name}",
)
}
Box(
modifier = Modifier
.weight(2f)
.fillMaxHeight()
.padding(6.dp)
.background(Color.Green)
) {
Column(modifier = Modifier.align(Alignment.TopStart)) {
Text(
text = "${product.number}. ${product.name}",
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.h4,
)
Text(
text = product.ingredients, fontStyle = FontStyle.Italic
)
}
Button(
modifier = Modifier.align(Alignment.BottomEnd),
onClick = {
onAddToCardButton()
},
shape = RoundedCornerShape(8.dp),
) {
if (product.type == "pizza") {
Text(text = "od ${String.format("%.2f", product.price[0])} zł")
} else {
Text(text = "${String.format("%.2f", product.price[0])} zł")
}
}
}
}
}
}
I expect you display this item in LazyColumn or inside a vertical scrollable.
Modifier.fillMaxHeight doesn't work in this case, because parent height constraint is equal to infinity.
To solve this you ofc can use a static value, but in this case intrinsic measurements can be used to wrap content size.
Add Modifier.height(IntrinsicSize.Max) to your Row.
The code looks good to me at first sight, even tried it and the button is on the bottom right corner. Where do you define the height of your ProductItem card?
Maybe you can try to define it in the code you provided by changing the
.fillMaxSize()
modifier to
.fillMaxWidth()
.requiredHeight(**.dp)
at your first Card composable.
So it would look something like this:
#Composable
fun ProductItem(product: ProductModel, onItemClick: () -> Unit, onAddToCardButton: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.requiredHeight(**.dp)
.padding(7.dp)
.clickable { onItemClick() },
shape = MaterialTheme.shapes.large,
elevation = 4.dp
) {
...
}
As Phil Dukhov answered Modifier.height(IntrinsicSize.Max) works for me.
Just set the modifier to the Root element in your Row. In your case it's Card (change .fillMaxWidth() to .height(IntrinsicSize.Max)):
Card(
modifier = Modifier
.height(IntrinsicSize.Max)
.padding(7.dp)
.clickable { onItemClick() },
shape = MaterialTheme.shapes.large,
elevation = 4.dp
)

Arrange a single item inside a LazyColumn by using weight modifier

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:

How remove extra padding above Jetpack Compose OutlinedTextField?

I'm trying to put OutlinedTextField into the Column after Box element like this
#Composable
fun Header() {
Column {
Box(
modifier = Modifier
.border(1.dp, Color.Cyan)
) {
Text("Header")
}
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.border(1.dp, Color.Cyan),
value = "",
onValueChange = {},
placeholder = {
Text("Enter header")
}
)
}
}
Borders added to see exact size of elements. It looks like this
with extra 8dp padding above, but when I use TextField instead of OutlinedTextField there are no extra space
#Composable
fun Header() {
Column {
Box(
modifier = Modifier
.border(1.dp, Color.Cyan)
) {
Text("Header")
}
TextField(
modifier = Modifier
.fillMaxWidth()
.border(1.dp, Color.Cyan),
value = "",
onValueChange = {},
placeholder = {
Text("Enter header")
}
)
}
}
I need to know why it is happening and how to solve it
Version of library is "androidx.compose.material:material:1.0.0-alpha10"
The Padding above OutlinedTextField is there because the label moves to the top when in focus, which requires space.
If you don't want this feature, you can create your own outlined text field composable by modifying BasicTextField.

Categories

Resources