英文:
Jetpack compose: customize radio button group to achieve segmented buttons
问题
iOS有类似这样的分段控件。我想在Android中使用Jetpack Compose实现这个,我检查了一下,发现Jetpack Compose中没有内置的类似控件,而且Material 3中的UI目前也不支持Jetpack Compose。我该怎么办?完全自定义单选按钮吗?我知道我可以添加背景和文本等内容,但如何隐藏选中标记,让单选按钮看起来像按钮呢?或者有没有可以用来实现类似分段控件UI的库?有人能给我一些建议吗?
英文:
iOS has Segmented controls like this.
I wanna have this in Android by using jetpack compose and I checked there is not build-in like this, and this UI in material 3 doesn't support jetpack compose now. What can I do? Totally customize the radio button? I know I can add the background and text and something, but how can I just hide check mark, make radio button looks like a button? Or there is something library I can use to use UI like Segmented controls? Can anyone give me some hints?
答案1
得分: 6
你可以使用一个Row
布局,将OutlinedButton
应用一个Offset
来避免双重边框。
类似这样:
Row(
modifier = Modifier
.fillMaxWidth()
) {
val cornerRadius = 16.dp
var selectedIndex by remember { mutableStateOf(-1) }
itemsList.forEachIndexed { index, item ->
OutlinedButton(
onClick = { selectedIndex = index },
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)
},
shape = when (index) {
0 -> RoundedCornerShape(
topStart = cornerRadius,
topEnd = 0.dp,
bottomStart = cornerRadius,
bottomEnd = 0.dp
)
itemsList.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) {
Blue500
} else {
Blue500.copy(alpha = 0.75f)
}
),
colors = if (selectedIndex == index) {
ButtonDefaults.outlinedButtonColors(
containerColor = Blue500.copy(alpha = 0.1f),
contentColor = Blue500
)
} else {
ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = Blue500
)
}
) {
Text("Button " + item)
}
}
}
英文:
You can use a Row
of OutlinedButton
applying an Offset
to avoid the double border.
Something like:
Row(
modifier = Modifier
.fillMaxWidth()
) {
val cornerRadius = 16.dp
var selectedIndex by remember { mutableStateOf(-1) }
itemsList.forEachIndexed { index, item ->
OutlinedButton(
onClick = { selectedIndex = index },
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)
},
shape = when (index) {
0 -> RoundedCornerShape(
topStart = cornerRadius,
topEnd = 0.dp,
bottomStart = cornerRadius,
bottomEnd = 0.dp
)
itemsList.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) {
Blue500
} else {
Blue500.copy(alpha = 0.75f)
}
),
colors = if (selectedIndex == index) {
ButtonDefaults.outlinedButtonColors(
containerColor = Blue500.copy(alpha = 0.1f),
contentColor = Blue500
)
} else {
ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = Blue500
)
}
) {
Text("Button " + item)
}
}
}
答案2
得分: 0
以下是翻译好的代码部分:
@Composable
fun SegmentedControl(
modifier: Modifier = Modifier,
items: List<String>,
defaultSelectedItemIndex: Int = 0,
useFixedWidth: Boolean = false,
itemWidth: Dp = 120.dp,
cornerRadius: Int = 24,
onItemSelection: (selectedItemIndex: Int) -> Unit
) {
val selectedIndex = remember { mutableStateOf(defaultSelectedItemIndex) }
val itemIndex = remember { mutableStateOf(defaultSelectedItemIndex) }
Card(
modifier = Modifier
.fillMaxWidth()
.height(38.dp),
colors = CardDefaults.cardColors(
containerColor = if (selectedIndex.value == itemIndex.value) {
MaterialTheme.colorScheme.background
} else {
MaterialTheme.colorScheme.secondary
}
),
shape = RoundedCornerShape(cornerRadius)
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.secondary),
horizontalArrangement = Arrangement.Center
) {
items.forEachIndexed { index, item ->
itemIndex.value = index
Card(
modifier = modifier
.weight(1f)
.padding(2.dp),
onClick = {
selectedIndex.value = index
onItemSelection(selectedIndex.value)
},
colors = CardDefaults.cardColors(
containerColor = if (selectedIndex.value == index) {
MaterialTheme.colorScheme.background
} else {
MaterialTheme.colorScheme.secondary
},
contentColor = if (selectedIndex.value == index)
MaterialTheme.colorScheme.scrim
else
MaterialTheme.colorScheme.onSecondary
),
shape = when (index) {
0 -> RoundedCornerShape(
topStartPercent = cornerRadius,
topEndPercent = cornerRadius,
bottomStartPercent = cornerRadius,
bottomEndPercent = cornerRadius
)
items.size - 1 -> RoundedCornerShape(
topStartPercent = cornerRadius,
topEndPercent = cornerRadius,
bottomStartPercent = cornerRadius,
bottomEndPercent = cornerRadius
)
else -> RoundedCornerShape(
topStartPercent = 0,
topEndPercent = 0,
bottomStartPercent = 0,
bottomEndPercent = 0
)
},
) {
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center){
TitleText(
text = item,
style = LocalTextStyle.current.copy(
fontSize = 14.sp,
fontWeight = if (selectedIndex.value == index)
LocalTextStyle.current.fontWeight
else
FontWeight.Normal,
color = if (selectedIndex.value == index)
MaterialTheme.colorScheme.scrim
else
MaterialTheme.colorScheme.onSecondary
),
textAlign = TextAlign.Center,
)
}
}
}
}
}
}
希望这有所帮助。如果您需要进一步的帮助,请随时告诉我。
英文:
If you want to be exactly the same as the swiftUI component, you can use this code:
@Composable
fun SegmentedControl(
modifier: Modifier = Modifier,
items: List<String>,
defaultSelectedItemIndex: Int = 0,
useFixedWidth: Boolean = false,
itemWidth: Dp = 120.dp,
cornerRadius: Int = 24,
onItemSelection: (selectedItemIndex: Int) -> Unit
) {
val selectedIndex = remember { mutableStateOf(defaultSelectedItemIndex) }
val itemIndex = remember { mutableStateOf(defaultSelectedItemIndex) }
Card(
modifier = Modifier
.fillMaxWidth()
.height(38.dp),
colors = CardDefaults.cardColors(
containerColor = if (selectedIndex.value == itemIndex.value) {
MaterialTheme.colorScheme.background
} else {
MaterialTheme.colorScheme.secondary
}
),
shape = RoundedCornerShape(cornerRadius)
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.secondary),
horizontalArrangement = Arrangement.Center
) {
items.forEachIndexed { index, item ->
itemIndex.value = index
Card(
modifier = modifier
.weight(1f)
.padding(2.dp),
onClick = {
selectedIndex.value = index
onItemSelection(selectedIndex.value)
},
colors = CardDefaults.cardColors(
containerColor = if (selectedIndex.value == index) {
MaterialTheme.colorScheme.background
} else {
MaterialTheme.colorScheme.secondary
},
contentColor = if (selectedIndex.value == index)
MaterialTheme.colorScheme.scrim
else
MaterialTheme.colorScheme.onSecondary
),
shape = when (index) {
0 -> RoundedCornerShape(
topStartPercent = cornerRadius,
topEndPercent = cornerRadius,
bottomStartPercent = cornerRadius,
bottomEndPercent = cornerRadius
)
items.size - 1 -> RoundedCornerShape(
topStartPercent = cornerRadius,
topEndPercent = cornerRadius,
bottomStartPercent = cornerRadius,
bottomEndPercent = cornerRadius
)
else -> RoundedCornerShape(
topStartPercent = 0,
topEndPercent = 0,
bottomStartPercent = 0,
bottomEndPercent = 0
)
},
) {
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center){
TitleText(
text = item,
style = LocalTextStyle.current.copy(
fontSize = 14.sp,
fontWeight = if (selectedIndex.value == index)
LocalTextStyle.current.fontWeight
else
FontWeight.Normal,
color = if (selectedIndex.value == index)
MaterialTheme.colorScheme.scrim
else
MaterialTheme.colorScheme.onSecondary
),
textAlign = TextAlign.Center,
)
}
}
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论