Reputation: 1049
I want to implement the MaterialButtonToggleGroup in Jetpack Compose. This component looks like this (image taken from here):
So far, I got the following result:
Note, that the vertical grey border next to the vertical blue border are present. In the original, either the colored border or the grey color are present at a time. To make it more clear, have a look at this image with extra thick borders:
How can I achieve that the vertical borders between two buttons are not present? My current code looks like this:
val cornerRadius = 8.dp
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Spacer(modifier = Modifier.weight(1f))
items.forEachIndexed { index, item ->
OutlinedButton(
onClick = { indexChanged(index) },
shape = when (index) {
// left outer button
0 -> RoundedCornerShape(topStart = cornerRadius, topEnd = 0.dp, bottomStart = cornerRadius, bottomEnd = 0.dp)
// right outer button
items.size - 1 -> RoundedCornerShape(topStart = 0.dp, topEnd = cornerRadius, bottomStart = 0.dp, bottomEnd = cornerRadius)
// middle button
else -> RoundedCornerShape(topStart = 0.dp, topEnd = 0.dp, bottomStart = 0.dp, bottomEnd = 0.dp)
},
border = BorderStroke(1.dp, if(selectedIndex == index) { MaterialTheme.colors.primary } else { Color.DarkGray.copy(alpha = 0.75f)}),
colors = if(selectedIndex == index) {
// selected colors
ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.primary.copy(alpha = 0.1f), contentColor = MaterialTheme.colors.primary)
} else {
// not selected colors
ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.surface, contentColor = MaterialTheme.colors.primary)
},
) {
Text(
text = "Some text",
color = if(selectedIndex == index) { MaterialTheme.colors.primary } else { Color.DarkGray.copy(alpha = 0.9f) },
modifier = Modifier.padding(horizontal = 8.dp)
)
}
}
Spacer(modifier = Modifier.weight(1f))
}
Upvotes: 12
Views: 3702
Reputation: 4040
It's now available in Material 3! Check the SingleChoiceSegmentedButtonRow
.
import androidx.compose.foundation.layout.size
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
var selectedIndex by remember { mutableStateOf(0) }
val options = listOf("Day", "Month", "Week")
SingleChoiceSegmentedButtonRow {
options.forEachIndexed { index, label ->
SegmentedButton(
shape = SegmentedButtonDefaults.itemShape(index = index, count = options.size),
onClick = { selectedIndex = index },
selected = index == selectedIndex
) {
Text(label)
}
}
}
Minor style changes required to match your design!
More info here in the official docs.
Upvotes: 1
Reputation: 1
@Composable
fun MaterialToggleButtonGroup(cornerRadius: Dp = 8.dp, list: List<String>, selectedIndex: Int, onSelectedIndexChange: (Int) -> Unit) {
Row {
list.forEachIndexed { index, item ->
OutlinedButton(
modifier = when (index) {
0 ->
Modifier
.offset(0.dp, 0.dp)
.zIndex(if (selectedIndex == index) 1f else 0f)
else ->
Modifier
.offset((-1 * index).dp, 0.dp)
.zIndex(if (selectedIndex == index) 1f else 0f)
},
onClick = { onSelectedIndexChange(index) },
shape = when (index) {
0 -> RoundedCornerShape(
topStart = cornerRadius,
topEnd = 0.dp,
bottomStart = cornerRadius,
bottomEnd = 0.dp
)
list.size - 1 -> RoundedCornerShape(
topStart = 0.dp,
topEnd = cornerRadius,
bottomStart = 0.dp,
bottomEnd = cornerRadius
)
else -> RoundedCornerShape(topStart = 0.dp, topEnd = 0.dp, bottomStart = 0.dp, bottomEnd = 0.dp)
},
border = BorderStroke(
1.dp, if (selectedIndex == index) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
}
),
colors = if (selectedIndex == index) {
ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
contentColor = MaterialTheme.colorScheme.primary
)
} else {
ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary
)
},
) {
Text(
text = item,
color = if (selectedIndex == index) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
}
)
}
}
}
}
Upvotes: 0
Reputation: 381
Combining the previous answer and the OP code, here's the full code (using material3):
(if you want to make the button group fill the width of the screen and for the buttons to share the width evenly, add .weight(1F).fillMaxWidth()
to the OutlinedButton
modifier. As is, the code will only take up as much width as necessary.)
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
@Composable
fun MaterialButtonToggleGroup(
items: List<String>,
onClick: (index: Int) -> Unit = {}
) {
val cornerRadius = 8.dp
val (selectedIndex, onIndexSelected) = remember { mutableStateOf<Int?>(null) }
Row(
modifier = Modifier.padding(8.dp)
) {
items.forEachIndexed { index, item ->
OutlinedButton(
modifier = when (index) {
0 ->
Modifier
.offset(0.dp, 0.dp)
.zIndex(if (selectedIndex == index) 1f else 0f)
else ->
Modifier
.offset((-1 * index).dp, 0.dp)
.zIndex(if (selectedIndex == index) 1f else 0f)
},
onClick = {
onIndexSelected(index)
onClick(index)
},
shape = when (index) {
// left outer button
0 -> RoundedCornerShape(
topStart = cornerRadius,
topEnd = 0.dp,
bottomStart = cornerRadius,
bottomEnd = 0.dp
)
// right outer button
items.size - 1 -> RoundedCornerShape(
topStart = 0.dp,
topEnd = cornerRadius,
bottomStart = 0.dp,
bottomEnd = cornerRadius
)
// middle button
else -> RoundedCornerShape(topStart = 0.dp, topEnd = 0.dp, bottomStart = 0.dp, bottomEnd = 0.dp)
},
border = BorderStroke(
1.dp, if (selectedIndex == index) {
MaterialTheme.colorScheme.primary
} else {
Color.DarkGray.copy(alpha = 0.75f)
}
),
colors = if (selectedIndex == index) {
// selected colors
ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
contentColor = MaterialTheme.colorScheme.primary
)
} else {
// not selected colors
ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary
)
},
) {
Text(
text = item,
color = if (selectedIndex == index) {
MaterialTheme.colorScheme.primary
} else {
Color.DarkGray.copy(alpha = 0.9f)
},
modifier = Modifier.padding(horizontal = 8.dp)
)
}
}
}
Upvotes: 2
Reputation: 365008
In the MaterialButtonToggleGroup
to prevent a double-width stroke there is a negative marginStart on all except the first child drawing the adjacent strokes directly on top of each other.
Using the same solution:
OutlinedButton(
modifier = when (index) {
0 ->
Modifier
.offset(0.dp, 0.dp)
.zIndex(if (selectedIndex == index) 1f else 0f)
else ->
Modifier
.offset((-1 * index).dp, 0.dp)
.zIndex(if (selectedIndex == index) 1f else 0f)
},
// Your code here
Upvotes: 10