SplitButtonLayout
Composable Component
A SplitButtonLayout
let user define a button group consisting of 2 buttons. The leading button
performs a primary action, and the trailing button performs a secondary action that is
contextually related to the primary action.
Common
@ExperimentalMaterial3ExpressiveApi
@Composable
fun SplitButtonLayout(
leadingButton: @Composable () -> Unit,
trailingButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
spacing: Dp = SplitButtonDefaults.Spacing,
)
Parameters
leadingButton | the leading button. You can specify your own composable or construct a SplitButtonDefaults.LeadingButton |
trailingButton | the trailing button.You can specify your own composable or construct a SplitButtonDefaults.TrailingButton |
modifier | the Modifier to be applied to this split button. |
spacing | The spacing between the leadingButton and trailingButton |
Code Examples
FilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun FilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
}
SplitButtonWithDropdownMenuSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithDropdownMenuSample() {
var checked by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxSize().wrapContentSize()) {
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
DropdownMenu(expanded = checked, onDismissRequest = { checked = false }) {
DropdownMenuItem(
text = { Text("Edit") },
onClick = { /* Handle edit! */ },
leadingIcon = { Icon(Icons.Outlined.Edit, contentDescription = null) },
)
DropdownMenuItem(
text = { Text("Settings") },
onClick = { /* Handle settings! */ },
leadingIcon = { Icon(Icons.Outlined.Settings, contentDescription = null) },
)
HorizontalDivider()
DropdownMenuItem(
text = { Text("Send Feedback") },
onClick = { /* Handle send feedback! */ },
leadingIcon = { Icon(Icons.Outlined.Email, contentDescription = null) },
trailingIcon = { Text("F11", textAlign = TextAlign.Center) },
)
}
}
}
TonalSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun TonalSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.TonalLeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TonalTrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
}
ElevatedSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun ElevatedSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.ElevatedLeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.ElevatedTrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
}
OutlinedSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun OutlinedSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.OutlinedLeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.OutlinedTrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
}
SplitButtonWithUnCheckableTrailingButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithUnCheckableTrailingButtonSample() {
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.KeyboardArrowDown,
modifier = Modifier.size(SplitButtonDefaults.TrailingIconSize),
contentDescription = "Localized description",
)
}
},
)
}
SplitButtonWithTextSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithTextSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(onClick = { /* Do Nothing */ }) { Text("My Button") }
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
}
SplitButtonWithIconSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithIconSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(onClick = { /* Do Nothing */ }) {
Icon(
Icons.Filled.Edit,
contentDescription = "Localized description",
Modifier.size(SplitButtonDefaults.LeadingIconSize),
)
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description",
)
}
},
)
}
XSmallFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun XSmallFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.ExtraSmallContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("My Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
MediumFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun MediumFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.MediumContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("My Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
LargeFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun LargeFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.LargeContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("My Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
ExtraLargeFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun ExtraLargeFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.ExtraLargeContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}