How do I get a good form layout in Compose?

82 Views Asked by At

Here is something that many applications end up adding: an options screen.

Sample program:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication

fun main() = singleWindowApplication {
    MaterialTheme {
        val (showSwitch1, setShowSwitch1) = remember { mutableStateOf(false) }
        val (showSwitch2, setShowSwitch2) = remember { mutableStateOf(true) }
        val (showSwitch3, setShowSwitch3) = remember { mutableStateOf(true) }

        Column(modifier = Modifier.padding(8.dp)) {
            @Composable
            fun booleanSettingsItem(text: String, value: Boolean, valueSetter: (Boolean) -> Unit) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(text)
                    Switch(value, valueSetter)
                }
            }

            booleanSettingsItem(
                "A switch",
                showSwitch1, setShowSwitch1
            )
            booleanSettingsItem(
                "Another switch",
                showSwitch2, setShowSwitch2
            )
            booleanSettingsItem(
                "A third switch with an even longer label",
                showSwitch3, setShowSwitch3
            )
        }
    }
}

The result:

screenshot showing the three switches all out of alignment

But as you can see, the switches don't line up.

What's the proper way to get a two-column layout so that all the value fields align? (Over in Swing, you can lean heavily on GroupLayout, and forms become fairly simple, if verbose.)

I did try some of the "grid" layouts provided by Compose, but they really seem more tailored to situations where every cell is the same size..? If that makes any sense.

Attempt 1: @JakubMroziński 's answer suggests putting two columns inside the row. That does sound logical as far as how you would lay it out on paper, so here we go, new sample program:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.window.singleWindowApplication

fun main() = singleWindowApplication {
    MaterialTheme {
        val (showSwitch1, setShowSwitch1) = remember { mutableStateOf(false) }
        val (showSwitch2, setShowSwitch2) = remember { mutableStateOf(true) }
        val (showSwitch3, setShowSwitch3) = remember { mutableStateOf(true) }

        Row {
            Column {
                Text("A switch")
                Text("Another switch")
                Text("A third switch with an even longer label")
            }

            Column {
                Switch(showSwitch1, setShowSwitch1)
                Switch(showSwitch2, setShowSwitch2)
                Switch(showSwitch3, setShowSwitch3)
            }
        }
    }
}

But now the result is:

screenshot showing the labels scrunched up against the top and the switches being spaced out more on the right.

This makes sense, because now there is no row to align the labels and the switches.

Attempt 2: The other similar question had an answer which suggested using LazyVerticalGrid. So here is a version using that.

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication

fun main() = singleWindowApplication {
    MaterialTheme {
        val (showSwitch1, setShowSwitch1) = remember { mutableStateOf(false) }
        val (showSwitch2, setShowSwitch2) = remember { mutableStateOf(true) }
        val (showSwitch3, setShowSwitch3) = remember { mutableStateOf(true) }

        LazyVerticalGrid(
            modifier = Modifier.padding(16.dp),
            columns = GridCells.Fixed(2)
        ) {
            item { Text("A switch") }
            item { Switch(showSwitch1, setShowSwitch1) }
            item { Text("Another switch") }
            item { Switch(showSwitch2, setShowSwitch2) }
            item { Text("A third switch with an even longer label") }
            item { Switch(showSwitch3, setShowSwitch3) }
        }
    }
}

The result:

screenshot showing the labels and switches aligned but with too much spacing around the switches

It's nearly good enough, but I want the first column to take as much space as possible, and the second column to take only what's necessary. Is there a way? Or do I have to resort to custom layout?

1

There are 1 best solutions below

1
On

For simple layouts in Compose it is enough to use only Row and Column functions.

If you want to create two columns, you simply create a Row and put two Columns inside, like that:

Row {
    Column { ... }
    Column { ... }
}

If you want your layout to take whole window size, you can put Modifier.fillMaxSize() as Row's modifier. You can also adjust each column's width using Modifier.fillMaxWidth(screenPercentage).

To align content in column you should use horizontalAlignment parameter of Column function. If you want it to be centered, just use Alignment.CenterHorizontally.