1+ library chips_choice;
2+
3+ import 'package:flutter/material.dart' ;
4+ import 'package:flutter/rendering.dart' ;
5+
6+ /// Easy way to provide a single or multiple choice chips.
7+ class ChipsChoice <T > extends StatelessWidget {
8+
9+ /// List of option item
10+ final List <ChipsChoiceOption <T >> options;
11+
12+ /// Choice item config
13+ final ChipsChoiceItemConfig itemConfig;
14+
15+ /// Builder for custom choice item
16+ final ChipsChoiceBuilder <T > itemBuilder;
17+
18+ /// List padding
19+ final EdgeInsetsGeometry padding;
20+
21+ final T _value;
22+ final List <T > _values;
23+ final ChipsChoiceChanged <T > _onChangedSingle;
24+ final ChipsChoiceChanged <List <T >> _onChangedMultiple;
25+ final bool _isMultiChoice;
26+
27+ ChipsChoice .single ({
28+ Key key,
29+ @required T value,
30+ @required this .options,
31+ @required ChipsChoiceChanged <T > onChanged,
32+ this .itemConfig = const ChipsChoiceItemConfig (),
33+ this .itemBuilder,
34+ this .padding,
35+ }) : assert (onChanged != null ),
36+ _isMultiChoice = false ,
37+ _value = value,
38+ _values = null ,
39+ _onChangedMultiple = null ,
40+ _onChangedSingle = onChanged,
41+ super (key: key);
42+
43+ ChipsChoice .multiple ({
44+ Key key,
45+ @required List <T > value,
46+ @required this .options,
47+ @required ChipsChoiceChanged <List <T >> onChanged,
48+ this .itemConfig = const ChipsChoiceItemConfig (),
49+ this .itemBuilder,
50+ this .padding,
51+ }) : assert (onChanged != null && value != null ),
52+ _isMultiChoice = true ,
53+ _value = null ,
54+ _values = value,
55+ _onChangedSingle = null ,
56+ _onChangedMultiple = onChanged,
57+ super (key: key);
58+
59+ @override
60+ Widget build (BuildContext context) {
61+ return itemConfig.isWrapped == true
62+ ? _listWrapped ()
63+ : _listScrollable ();
64+ }
65+
66+ Widget _listScrollable () {
67+ return SingleChildScrollView (
68+ scrollDirection: Axis .horizontal,
69+ padding: padding ?? EdgeInsets .symmetric (horizontal: 10 ),
70+ child: Row (
71+ children: _choiceItems,
72+ ),
73+ );
74+ }
75+
76+ Widget _listWrapped () {
77+ return Padding (
78+ padding: padding ?? EdgeInsets .symmetric (
79+ vertical: 10.0 ,
80+ horizontal: 15.0 ,
81+ ),
82+ child: Wrap (
83+ spacing: itemConfig.wrappedSpacing, // gap between adjacent chips
84+ runSpacing: itemConfig.wrappedRunSpacing, // gap between lines
85+ children: _choiceItems,
86+ ),
87+ );
88+ }
89+
90+ List <Widget > get _choiceItems {
91+ return List <Widget >.generate (
92+ options.length,
93+ (i) => _choiceItemBuilder (i),
94+ ).where ((item) => item != null ).toList ();
95+ }
96+
97+ Widget _choiceItemBuilder (int i) {
98+ ChipsChoiceOption <T > item = options[i];
99+ bool selected = _isMultiChoice
100+ ? _values.contains (item.value)
101+ : _value == item.value;
102+ return item.hidden == false
103+ ? itemBuilder? .call (item, itemConfig, selected) ?? _ChipsChoiceItem (
104+ item: item,
105+ config: itemConfig,
106+ selected: selected,
107+ onSelect: _onSelect,
108+ )
109+ : null ;
110+ }
111+
112+ void _onSelect (T value, bool selected) {
113+ if (_isMultiChoice) {
114+ List <T > values = List .from (_values ?? []);
115+ if (selected) {
116+ values.add (value);
117+ } else {
118+ values.remove (value);
119+ }
120+ _onChangedMultiple? .call (values);
121+ } else {
122+ _onChangedSingle? .call (value);
123+ }
124+ }
125+ }
126+
127+ class _ChipsChoiceItem <T > extends StatelessWidget {
128+
129+ final ChipsChoiceOption <T > item;
130+ final ChipsChoiceItemConfig config;
131+ final Function onSelect;
132+ final bool selected;
133+
134+ _ChipsChoiceItem ({
135+ Key key,
136+ @required this .item,
137+ @required this .config,
138+ @required this .onSelect,
139+ @required this .selected,
140+ }) : super (key: key);
141+
142+ @override
143+ Widget build (BuildContext context) {
144+ ThemeData _theme = Theme .of (context);
145+ Color _selectedColor = config.selectedColor ?? _theme.primaryColor;
146+ Color _unselectedColor = config.unselectedColor ?? _theme.unselectedWidgetColor;
147+
148+ return Padding (
149+ padding: config.margin,
150+ child: FilterChip (
151+ label: Text (
152+ item.label,
153+ style: TextStyle (
154+ color: selected ? _selectedColor : _unselectedColor,
155+ ),
156+ ),
157+ avatar: item.avatar,
158+ shape: config.shapeBuilder? .call (selected) ?? StadiumBorder (
159+ side: BorderSide (
160+ color: selected
161+ ? _selectedColor.withOpacity (.2 )
162+ : _unselectedColor.withOpacity (.2 ),
163+ ),
164+ ),
165+ elevation: config.elevation,
166+ pressElevation: config.pressElevation,
167+ shadowColor: _unselectedColor,
168+ selectedShadowColor: _selectedColor,
169+ backgroundColor: Colors .transparent,
170+ selectedColor: Colors .transparent,
171+ checkmarkColor: _selectedColor,
172+ showCheckmark: config.showCheckmark == true ,
173+ selected: selected,
174+ onSelected: item.disabled == false
175+ ? (_selected) => onSelect (item.value, _selected)
176+ : null ,
177+ ),
178+ );
179+ }
180+ }
181+
182+ class ChipsChoiceOption <T > {
183+ final T value;
184+ final String label;
185+ final Widget avatar;
186+ final bool disabled;
187+ final bool hidden;
188+
189+ ChipsChoiceOption ({
190+ @required this .value,
191+ @required this .label,
192+ this .avatar,
193+ this .disabled = false ,
194+ this .hidden = false ,
195+ }) : assert (disabled != null ),
196+ assert (hidden != null );
197+
198+ static List <ChipsChoiceOption <R >> listFrom <S , R >({
199+ @required List <S > source,
200+ @required ChipsChoiceOptionProp <S , R > value,
201+ @required ChipsChoiceOptionProp <S , String > label,
202+ ChipsChoiceOptionProp <S , Widget > avatar,
203+ ChipsChoiceOptionProp <S , bool > disabled,
204+ ChipsChoiceOptionProp <S , bool > hidden,
205+ }) => source
206+ .asMap ()
207+ .map ((index, item) => MapEntry (index, ChipsChoiceOption <R >(
208+ value: value? .call (index, item),
209+ label: label? .call (index, item),
210+ avatar: avatar? .call (index, item),
211+ disabled: disabled? .call (index, item) ?? false ,
212+ hidden: hidden? .call (index, item) ?? false ,
213+ )))
214+ .values
215+ .toList ()
216+ .cast <ChipsChoiceOption <R >>();
217+ }
218+
219+ class ChipsChoiceItemConfig {
220+ final bool isWrapped;
221+ final bool showCheckmark;
222+ final Color selectedColor;
223+ final Color unselectedColor;
224+ final EdgeInsetsGeometry margin;
225+ final double wrappedSpacing;
226+ final double wrappedRunSpacing;
227+ final double elevation;
228+ final double pressElevation;
229+ final ChipsChoiceShapeBuilder shapeBuilder;
230+
231+ const ChipsChoiceItemConfig ({
232+ this .isWrapped = false ,
233+ this .showCheckmark = true ,
234+ this .selectedColor,
235+ this .unselectedColor,
236+ this .margin = const EdgeInsets .all (5 ),
237+ this .wrappedSpacing = 10.0 ,
238+ this .wrappedRunSpacing = 0 ,
239+ this .elevation = 0 ,
240+ this .pressElevation = 0 ,
241+ this .shapeBuilder,
242+ });
243+ }
244+
245+ typedef ShapeBorder ChipsChoiceShapeBuilder (bool selected);
246+ typedef R ChipsChoiceOptionProp <T , R >(int index, T item);
247+ typedef void ChipsChoiceChanged <T >(T values);
248+ typedef Widget ChipsChoiceBuilder <T >(
249+ ChipsChoiceOption <T > item,
250+ ChipsChoiceItemConfig config,
251+ bool selected
252+ );
0 commit comments