Skip to content

Instantly share code, notes, and snippets.

@exavolt
Last active November 8, 2024 15:24
Show Gist options
  • Save exavolt/8b303b9d556f1cf74f2282917b79a631 to your computer and use it in GitHub Desktop.
Save exavolt/8b303b9d556f1cf74f2282917b79a631 to your computer and use it in GitHub Desktop.
ChoiceChipBar

Choice Chip Bar for Flutter

While Flutter already provides ChoiceChip, it's a bare-bone component. We would need to wire them ourselves to create a group of choices. This widget, ChoiceChipBar, means to provide a self-contained widget for a group of choice chips, so the ancestor widget won't need to manage the state; just provide the choices and a callback to onChanged.

Screenshot_1594966144 copy

//
import 'package:flutter/material.dart';
// https://material.io/components/chips#choice-chips
class ChoiceChipBar<T> extends StatefulWidget {
ChoiceChipBar({
Key key,
@required this.choices,
this.initialValue,
this.allowDeselect,
this.onChanged,
this.choiceText,
this.choiceLabel,
this.choiceAvatar,
this.padding,
this.color,
this.shape,
this.labelStyle,
this.selectedColor,
this.selectedShape,
this.selectedLabelStyle,
}) : assert(choices.isNotEmpty == true),
super(key: key);
final List<T> choices;
final T initialValue;
final bool allowDeselect;
final ValueChanged<T> onChanged;
// A callback to generate label text for each choice. This will be ignored
// if [choiceLabel] is not null.
final String Function(T e) choiceText;
// Widget builder used to create the label for each choice. This overrides
// [choiceText]. The returned widget must not be null.
final Widget Function(BuildContext context, T e, bool selected) choiceLabel;
// Widget builder used to create the avatar for each choice.
final Widget Function(BuildContext context, T e, bool selected) choiceAvatar;
final EdgeInsets padding;
final Color color;
final ShapeBorder shape;
final TextStyle labelStyle;
final Color selectedColor;
final ShapeBorder selectedShape;
final TextStyle selectedLabelStyle;
@override
_ChoiceChipBarState<T> createState() => _ChoiceChipBarState<T>();
}
class _ChoiceChipBarState<T> extends State<ChoiceChipBar<T>> {
T _value;
@override
void initState() {
super.initState();
_value = widget.initialValue ?? widget.choices.first;
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final contentWidget = Wrap(
spacing: 8,
children: widget.choices.map<Widget>((e) {
final selected = _value == e;
return ChoiceChip(
backgroundColor: widget.color,
selectedColor: widget.selectedColor,
shape: selected ? widget.selectedShape : widget.shape,
avatar: widget.choiceAvatar?.call(context, e, selected),
label: widget.choiceLabel == null
? Text(
widget.choiceText == null ? '$e' : widget.choiceText(e),
style: selected
? widget.selectedLabelStyle
: widget.labelStyle,
)
: widget.choiceLabel(context, e, selected),
selected: selected,
onSelected: (bool on) {
if (!on && !widget.allowDeselect) {
return;
}
// Can we, rather than calling setState, rebuild only the
// the widgets which we move the selection from and to?
setState(() {
_value = on ? e : null;
widget.onChanged?.call(_value);
});
},
);
}).toList(growable: false));
final paddedContent = widget.padding != null
? Padding(
padding: widget.padding,
child: contentWidget,
)
: contentWidget;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: paddedContent,
);
}
}
//
import 'package:flutter/material.dart';
import 'choice_chip_bar.dart';
class ChoiceChipBarPage extends StatefulWidget {
static Route<void> createRoute(RouteSettings settings) => MaterialPageRoute(
builder: (BuildContext context) => ChoiceChipBarPage(),
settings: settings,
);
@override
_ChoiceChipBarPageState createState() => _ChoiceChipBarPageState();
}
class _ChoiceChipBarPageState extends State<ChoiceChipBarPage> {
Hardness _selectedHardness = Hardness.medium;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Choice Chip Bar'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Select type',
style: theme.textTheme.subtitle1,
),
),
ChoiceChipBar<Hardness>(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
choices: Hardness.values,
initialValue: _selectedHardness,
onChanged: (Hardness selected) {
setState(() {
_selectedHardness = selected;
});
},
choiceText: (Hardness c) {
switch (c) {
case Hardness.extraSoft:
return 'Extra Soft';
case Hardness.soft:
return 'Soft';
case Hardness.medium:
return 'Medium';
case Hardness.hard:
return 'Hard';
case Hardness.extraHard:
return 'Extra Hard';
default:
return '<undefined>';
}
},
),
],
),
),
);
}
}
enum Hardness {
extraSoft,
soft,
medium,
hard,
extraHard,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment