This tutorial shows you how to use the SegmentedButton
widget in Flutter.
Segmented button is a Material Design component that allows users to select options, switch views, or sort elements. Common usages include displaying product options, filter options, and tab switcher. Flutter already provides a widget called SegmentedButton
. By using the widget, you can easily implement Material Design's segmented button. Below are the usage examples including how to customize the behavior and appearance.
Using SegmentedButton
To use the SegmentedButton
widget, you need to call the constructor. The constructor has two required named arguments, segments
and selected
.
For the segments
argument, you have to pass a List
of ButtonSegment
s. Each ButtonSegment
represents a segment that can be selected. The ButtonSegment
itself has a type parameter where you need to specify the type of the values that can be selected.
To create a ButtonSegment
, call the constructor. For the value
argument, you need to pass a value that complies with the type parameter. To display a text in the button, you can pass the label
argument. It's also possible to set an icon by passing a widget as the icon
argument. For each button, you can choose to pass label
, icon
, or both. To control whether a segment is selectable, you can set the value of the enabled
argument which defaults to true
.
For the selected
argument of SegmentedButton
, you need to pass a Set
whose element type is the same as the type parameter. It contains the currently selected values. The ButtonSegment
can be configured to allow multiple selections using the multiSelectionEnabled
argument which will be exemplified later. Below is a basic example that supports single selection and only passes the required arguments.
enum Color { red, green, blue, yellow }
class ColorOptions extends StatefulWidget {
const ColorOptions({super.key});
@override
State<ColorOptions> createState() => _ColorOptionsState();
}
class _ColorOptionsState extends State<ColorOptions> {
Color _selectedColor = Color.blue;
@override
Widget build(BuildContext context) {
return SegmentedButton<Color>(
segments: const <ButtonSegment<Color>>[
ButtonSegment<Color>(
value: Color.red,
label: Text('Red'),
),
ButtonSegment<Color>(
value: Color.green,
label: Text('Green'),
),
ButtonSegment<Color>(
value: Color.blue,
label: Text('Blue'),
),
ButtonSegment<Color>(
value: Color.yellow,
label: Text('Yellow'),
),
],
selected: <Color>{_selectedColor},
);
}
}
Output:
If you see the screenshot above, the buttons are disabled. That's because the onSelectionChanged
argument is null. Despite not being required, the onSelectionChanged
argument should be passed in almost all cases. I am going to explain it in the next section.
Handle Selection Change
Flutter will consider a segment is being selected based on the value of the selected
argument. When the user selects or unselects a segment, the Set
passed to the selected
argument may need to be updated. Flutter doesn't do that automatically, so it's your responsibility to update the value.
To handle selection change, pass a function as the onSelectionChanged
argument. When the selection changes, the function will be triggered and you can get the latest selection from the argument passed to the function. Inside the function, you have to update the selected
value. A common implementation is to store the value in a state variable, so that it can be updated using setState
when the onSelectionChanged
function is called.
SegmentedButton<Accessory>(
// other arguments
onSelectionChanged: (Set<Color> newSelection) {
setState(() {
_selectedColor = newSelection.first;
});
},
)
Output:
Allow Multiple Selections
The default behavior is that only one item can be selected at the same time. To support multiple choices, set the multiSelectionEnabled
to true. Below is another example that allows multiple selections.
enum Accessory { monitor, keyboard, mouse }
class AccessoryOptions extends StatefulWidget {
const AccessoryOptions({super.key});
@override
State<AccessoryOptions> createState() => _AccessoryOptionsState();
}
class _AccessoryOptionsState extends State<AccessoryOptions> {
Set<Accessory> selectedAccessories = <Accessory>{Accessory.keyboard};
@override
Widget build(BuildContext context) {
return SegmentedButton<Accessory>(
selected: selectedAccessories,
multiSelectionEnabled: true,
onSelectionChanged: (Set<Accessory> newSelection) {
setState(() {
selectedAccessories = newSelection;
});
},
segments: const <ButtonSegment<Accessory>>[
ButtonSegment<Accessory>(
value: Accessory.monitor,
label: Text('Monitor'),
icon: Icon(Icons.monitor),
),
ButtonSegment<Accessory>(
value: Accessory.keyboard,
label: Text('Keyboard'),
icon: Icon(Icons.keyboard),
),
ButtonSegment<Accessory>(
value: Accessory.mouse,
label: Text('Mouse'),
icon: Icon(Icons.mouse),
),
],
);
}
}
Output:
Allow Empty Selection
By default, empty selection is disabled. That means if currently there is only one item selected and you click on that selected item, it will not be unselected. If you want to allow empty selection, pass the emptySelectionAllowed
argument and set the value to true
.
SegmentedButton<Accessory>(
// other arguments
emptySelectionAllowed: true,
)
Output:
Configure Selected Icon
When an item is selected, the default appearance is a selected icon will be shown. If the button has an icon, it will be replaced by the selected icon. If you want to keep the button's icon or not showing the selected icon, it can be done by setting the showSelectedIcon
to false
.
SegmentedButton<Accessory>(
// other arguments
showSelectedIcon: false,
)
Output:
The default icon is a check mark (Icons.check
). It can be changed with another icon passed as the selectedIcon
argument. You can also create a custom icon.
SegmentedButton<Accessory>(
// other arguments
selectedIcon: const Icon(Icons.check_circle),
)
Output:
Custom Style
A ButtonStyle
value can be passed to the style
argument to customize the appearance of the buttons. The example below sets custom colors for the background and foreground.
SegmentedButton<Accessory>(
// other arguments
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.selected)
? Colors.teal
: Colors.white;
}),
foregroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.selected)
? Colors.white
: Colors.teal;
}),
),
)
Output:
SegmentedButton
Parameters
Below is the list of parameters you can pass to the constructor of SegmentedButton
.
Key? key
: The widget's key, used to control how a widget is replaced with another.required List<ButtonSegment<T>> segments
: List of segments.required Set<T> selected
: Set of selected segments.Function(Set<T>)? onSelectionChanged
: Function to be called when the selection changes.bool multiSelectionEnabled
: Whether to allow multiple segment selection. Defaults tofalse
.bool emptySelectionAllowed
: Whether it's allowed to have no segment selected. Defaults tofalse
.ButtonStyle? style
: Style for customizing the button.bool showSelectedIcon
: Whether the selected icon should be displayed on the selected segments. Defaults tofalse
.Widget? selectedIcon
: Icon used to indicate whether a segment is selected. Defaults toIcons.check
.
ButtonSegment
Parameters
Below is the list of parameters you can pass to the constructor of ButtonSegment
.
required T value
: Value for identifying the segment.Widget? icon
: Icon to be displayed in the segment (optional).Widget? label
: Label to be displayed in the segment (optional).bool enabled
: Whether the segment is available for selection. Defaults totrue
.
Full Code
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Woolha.com Flutter Tutorial',
home: SegmentedButtonExample(),
);
}
}
class SegmentedButtonExample extends StatelessWidget{
const SegmentedButtonExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Woolha.com Flutter Tutorial'),
backgroundColor: Colors.teal,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Spacer(),
Text('Color options'),
ColorOptions(),
SizedBox(height: 20),
Text('Accessory options'),
AccessoryOptions(),
Spacer(),
],
),
),
);
}
}
ButtonStyle buttonStyle = ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.selected)
? Colors.teal
: Colors.white;
}),
foregroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.selected)
? Colors.white
: Colors.teal;
}),
);
enum Color { red, green, blue, yellow }
class ColorOptions extends StatefulWidget {
const ColorOptions({super.key});
@override
State<ColorOptions> createState() => _ColorOptionsState();
}
class _ColorOptionsState extends State<ColorOptions> {
Color _selectedColor = Color.blue;
@override
Widget build(BuildContext context) {
return SegmentedButton<Color>(
selected: <Color>{_selectedColor},
onSelectionChanged: (Set<Color> newSelection) {
setState(() {
_selectedColor = newSelection.first;
});
},
segments: const <ButtonSegment<Color>>[
ButtonSegment<Color>(
value: Color.red,
label: Text('Red'),
),
ButtonSegment<Color>(
value: Color.green,
label: Text('Green'),
),
ButtonSegment<Color>(
value: Color.blue,
label: Text('Blue'),
),
ButtonSegment<Color>(
value: Color.yellow,
label: Text('Yellow'),
),
],
// style: buttonStyle,
);
}
}
enum Accessory { monitor, keyboard, mouse }
class AccessoryOptions extends StatefulWidget {
const AccessoryOptions({super.key});
@override
State<AccessoryOptions> createState() => _AccessoryOptionsState();
}
class _AccessoryOptionsState extends State<AccessoryOptions> {
Set<Accessory> selectedAccessories = <Accessory>{Accessory.keyboard};
@override
Widget build(BuildContext context) {
return SegmentedButton<Accessory>(
selected: selectedAccessories,
onSelectionChanged: (Set<Accessory> newSelection) {
setState(() {
selectedAccessories = newSelection;
});
},
multiSelectionEnabled: true,
emptySelectionAllowed: true,
showSelectedIcon: false,
selectedIcon: const Icon(Icons.check_circle),
style: buttonStyle,
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.selected)
? Colors.teal
: Colors.white;
}),
foregroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.selected)
? Colors.white
: Colors.teal;
}),
),
segments: const <ButtonSegment<Accessory>>[
ButtonSegment<Accessory>(
value: Accessory.monitor,
label: Text('Monitor'),
icon: Icon(Icons.monitor),
),
ButtonSegment<Accessory>(
value: Accessory.keyboard,
label: Text('Keyboard'),
icon: Icon(Icons.keyboard),
),
ButtonSegment<Accessory>(
value: Accessory.mouse,
label: Text('Mouse'),
icon: Icon(Icons.mouse),
),
],
);
}
}
Summary
Flutter's SegmentedButton
widget makes it easy to implement Material Design's segmented button. The usage is quite simple, just call the constructor by defining the segments and the currently active segments. You also need to pass a function that handles selection changes. It supports both single choice and multiple choices depending on the configuration. You can also enable empty selection, change the selected icon, and customize the style of the buttons.