Skip to content

Commit c94ec60

Browse files
committed
Add itemDecoration to checkbox groups nad radio groups
1 parent e518c0e commit c94ec60

8 files changed

+204
-6
lines changed

example/lib/main.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:example/sources/conditional_fields.dart';
2+
import 'package:example/sources/decorated_radio_checkbox.dart';
23
import 'package:example/sources/dynamic_fields.dart';
34
import 'package:example/sources/related_fields.dart';
45
import 'package:flutter/material.dart';
@@ -141,6 +142,23 @@ class _HomePage extends StatelessWidget {
141142
);
142143
},
143144
),
145+
const Divider(),
146+
ListTile(
147+
title: const Text('Radio Checkbox itemDecorator'),
148+
trailing: const Icon(Icons.arrow_right_sharp),
149+
onTap: () {
150+
Navigator.of(context).push(
151+
MaterialPageRoute(
152+
builder: (context) {
153+
return const CodePage(
154+
title: 'ItemDecorators',
155+
child: DecoratedRadioCheckbox(),
156+
);
157+
},
158+
),
159+
);
160+
},
161+
),
144162
],
145163
),
146164
);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_form_builder/flutter_form_builder.dart';
3+
4+
class DecoratedRadioCheckbox extends StatefulWidget {
5+
const DecoratedRadioCheckbox({Key? key}) : super(key: key);
6+
7+
@override
8+
State<DecoratedRadioCheckbox> createState() => _DecoratedRadioCheckboxState();
9+
}
10+
11+
class _DecoratedRadioCheckboxState extends State<DecoratedRadioCheckbox> {
12+
final _formKey = GlobalKey<FormBuilderState>();
13+
int? option;
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
return FormBuilder(
18+
key: _formKey,
19+
child: Column(
20+
children: <Widget>[
21+
const SizedBox(height: 20),
22+
FormBuilderCheckboxGroup(
23+
name: 'aCheckboxGroup',
24+
options: getDemoOptionsWidgets(),
25+
wrapSpacing: 5.0,
26+
itemDecoration: BoxDecoration(
27+
border: Border.all(color: Colors.blueAccent),
28+
borderRadius: BorderRadius.circular(5.0)),
29+
),
30+
const SizedBox(height: 20),
31+
FormBuilderCheckboxGroup(
32+
name: 'aCheckboxGroup',
33+
options: getDemoOptionsWidgets(),
34+
wrapSpacing: 5.0,
35+
controlAffinity: ControlAffinity.trailing,
36+
itemDecoration: BoxDecoration(
37+
border: Border.all(color: Colors.blueAccent),
38+
borderRadius: BorderRadius.circular(5.0)),
39+
),
40+
const SizedBox(height: 20),
41+
FormBuilderRadioGroup(
42+
name: 'aRadioGroup',
43+
options: getDemoOptionsWidgets(),
44+
wrapSpacing: 5.0,
45+
itemDecoration: BoxDecoration(
46+
border: Border.all(color: Colors.blueAccent),
47+
borderRadius: BorderRadius.circular(5.0)),
48+
),
49+
const SizedBox(height: 20),
50+
FormBuilderRadioGroup(
51+
name: 'aRadioGroup2',
52+
options: getDemoOptions(),
53+
wrapSpacing: 5.0,
54+
itemDecoration: BoxDecoration(
55+
border: Border.all(color: Colors.blueAccent),
56+
borderRadius: BorderRadius.circular(5.0)),
57+
),
58+
const SizedBox(height: 20),
59+
],
60+
),
61+
);
62+
}
63+
64+
List<FormBuilderFieldOption> getDemoOptionsWidgets() {
65+
return const [
66+
FormBuilderFieldOption(
67+
value: "airplane",
68+
child: Column(
69+
children: [Text("Airplane"), Icon(Icons.airplanemode_on)],
70+
)),
71+
FormBuilderFieldOption(
72+
value: "fire-truck",
73+
child:
74+
Column(children: [Text("Fire Truck"), Icon(Icons.fire_truck)])),
75+
FormBuilderFieldOption(
76+
value: "bus-alert",
77+
child: Column(children: [Text("Bus Alert"), Icon(Icons.bus_alert)])),
78+
FormBuilderFieldOption(
79+
value: "firetruck",
80+
child:
81+
Column(children: [Text("Motorcycle"), Icon(Icons.motorcycle)])),
82+
];
83+
}
84+
85+
List<FormBuilderFieldOption> getDemoOptions() {
86+
return const [
87+
FormBuilderFieldOption(
88+
value: "airplane",
89+
),
90+
FormBuilderFieldOption(
91+
value: "fire-truck",
92+
),
93+
FormBuilderFieldOption(
94+
value: "bus-alert",
95+
),
96+
FormBuilderFieldOption(
97+
value: "firetruck",
98+
),
99+
];
100+
}
101+
}

lib/src/fields/form_builder_checkbox_group.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class FormBuilderCheckboxGroup<T> extends FormBuilderFieldDecoration<List<T>> {
2525
final ControlAffinity controlAffinity;
2626
final OptionsOrientation orientation;
2727

28+
/// A decorator that is applied to each select item - ex: box the item
29+
final BoxDecoration? itemDecoration;
30+
2831
/// Creates a list of Checkboxes for selecting multiple options
2932
FormBuilderCheckboxGroup({
3033
super.key,
@@ -60,6 +63,7 @@ class FormBuilderCheckboxGroup<T> extends FormBuilderFieldDecoration<List<T>> {
6063
this.separator,
6164
this.controlAffinity = ControlAffinity.leading,
6265
this.orientation = OptionsOrientation.wrap,
66+
this.itemDecoration,
6367
}) : super(
6468
builder: (FormFieldState<List<T>?> field) {
6569
final state = field as _FormBuilderCheckboxGroupState<T>;
@@ -93,6 +97,7 @@ class FormBuilderCheckboxGroup<T> extends FormBuilderFieldDecoration<List<T>> {
9397
wrapVerticalDirection: wrapVerticalDirection,
9498
separator: separator,
9599
controlAffinity: controlAffinity,
100+
itemDecoration: itemDecoration,
96101
),
97102
);
98103
},

lib/src/fields/form_builder_radio_group.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class FormBuilderRadioGroup<T> extends FormBuilderFieldDecoration<T> {
2222
final WrapAlignment wrapRunAlignment;
2323
final WrapCrossAlignment wrapCrossAxisAlignment;
2424

25+
/// A decorator that is applied to each select item - ex: box the item
26+
final BoxDecoration? itemDecoration;
27+
2528
/// Creates field to select one value from a list of Radio Widgets
2629
FormBuilderRadioGroup({
2730
super.autovalidateMode = AutovalidateMode.disabled,
@@ -54,6 +57,7 @@ class FormBuilderRadioGroup<T> extends FormBuilderFieldDecoration<T> {
5457
super.valueTransformer,
5558
super.onReset,
5659
super.restorationId,
60+
this.itemDecoration,
5761
}) : super(
5862
builder: (FormFieldState<T?> field) {
5963
final state = field as _FormBuilderRadioGroupState<T>;
@@ -84,6 +88,7 @@ class FormBuilderRadioGroup<T> extends FormBuilderFieldDecoration<T> {
8488
wrapSpacing: wrapSpacing,
8589
wrapTextDirection: wrapTextDirection,
8690
wrapVerticalDirection: wrapVerticalDirection,
91+
itemDecoration: itemDecoration,
8792
),
8893
);
8994
},

lib/src/widgets/grouped_checkbox.dart

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ class GroupedCheckbox<T> extends StatelessWidget {
181181

182182
final ControlAffinity controlAffinity;
183183

184+
/// will be added to each item if provided
185+
final BoxDecoration? itemDecoration;
186+
184187
const GroupedCheckbox({
185188
super.key,
186189
required this.options,
@@ -205,13 +208,14 @@ class GroupedCheckbox<T> extends StatelessWidget {
205208
this.separator,
206209
this.controlAffinity = ControlAffinity.leading,
207210
this.visualDensity,
211+
this.itemDecoration,
208212
});
209213

210214
@override
211215
Widget build(BuildContext context) {
212216
final widgetList = <Widget>[];
213217
for (var i = 0; i < options.length; i++) {
214-
widgetList.add(item(i));
218+
widgetList.add(buildItem(i));
215219
}
216220
Widget finalWidget;
217221
if (orientation == OptionsOrientation.vertical) {
@@ -249,7 +253,8 @@ class GroupedCheckbox<T> extends StatelessWidget {
249253
return finalWidget;
250254
}
251255

252-
Widget item(int index) {
256+
/// the composite of all the components for the option at index
257+
Widget buildItem(int index) {
253258
final option = options[index];
254259
final optionValue = option.value;
255260
final isOptionDisabled = true == disabled?.contains(optionValue);
@@ -287,7 +292,7 @@ class GroupedCheckbox<T> extends StatelessWidget {
287292
child: option,
288293
);
289294

290-
return Row(
295+
Widget compositeItem = Row(
291296
mainAxisSize: MainAxisSize.min,
292297
children: <Widget>[
293298
if (controlAffinity == ControlAffinity.leading) control,
@@ -296,5 +301,14 @@ class GroupedCheckbox<T> extends StatelessWidget {
296301
if (separator != null && index != options.length - 1) separator!,
297302
],
298303
);
304+
305+
if (this.itemDecoration != null) {
306+
compositeItem = Container(
307+
decoration: this.itemDecoration,
308+
child: compositeItem,
309+
);
310+
}
311+
312+
return compositeItem;
299313
}
300314
}

lib/src/widgets/grouped_radio.dart

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ class GroupedRadio<T> extends StatefulWidget {
172172

173173
final ControlAffinity controlAffinity;
174174

175+
/// will be added to each item if provided
176+
final BoxDecoration? itemDecoration;
177+
175178
const GroupedRadio({
176179
super.key,
177180
required this.options,
@@ -193,6 +196,7 @@ class GroupedRadio<T> extends StatefulWidget {
193196
this.wrapVerticalDirection = VerticalDirection.down,
194197
this.separator,
195198
this.controlAffinity = ControlAffinity.leading,
199+
this.itemDecoration,
196200
});
197201

198202
@override
@@ -204,7 +208,7 @@ class _GroupedRadioState<T> extends State<GroupedRadio<T?>> {
204208
Widget build(BuildContext context) {
205209
final widgetList = <Widget>[];
206210
for (int i = 0; i < widget.options.length; i++) {
207-
widgetList.add(_buildRadioButton(i));
211+
widgetList.add(buildItem(i));
208212
}
209213

210214
switch (widget.orientation) {
@@ -239,7 +243,8 @@ class _GroupedRadioState<T> extends State<GroupedRadio<T?>> {
239243
}
240244
}
241245

242-
Widget _buildRadioButton(int index) {
246+
/// the composite of all the components for the option at index
247+
Widget buildItem(int index) {
243248
final option = widget.options[index];
244249
final optionValue = option.value;
245250
final isOptionDisabled = true == widget.disabled?.contains(optionValue);
@@ -266,7 +271,7 @@ class _GroupedRadioState<T> extends State<GroupedRadio<T?>> {
266271
child: option,
267272
);
268273

269-
return Column(
274+
Widget compositeItem = Column(
270275
mainAxisSize: MainAxisSize.min,
271276
crossAxisAlignment: CrossAxisAlignment.start,
272277
children: [
@@ -288,5 +293,14 @@ class _GroupedRadioState<T> extends State<GroupedRadio<T?>> {
288293
widget.separator!,
289294
],
290295
);
296+
297+
if (widget.itemDecoration != null) {
298+
compositeItem = Container(
299+
decoration: widget.itemDecoration,
300+
child: compositeItem,
301+
);
302+
}
303+
304+
return compositeItem;
291305
}
292306
}

test/form_builder_checkbox_group_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,27 @@ void main() {
2828
expect(formSave(), isTrue);
2929
expect(formValue(widgetName), equals(const [1, 3]));
3030
});
31+
32+
testWidgets('FormBuilderCheckboxGroup -- decoration',
33+
(WidgetTester tester) async {
34+
const widgetName = 'cbg1';
35+
final testWidget = FormBuilderCheckboxGroup<int>(
36+
name: widgetName,
37+
options: const [
38+
FormBuilderFieldOption(key: ValueKey('1'), value: 1),
39+
FormBuilderFieldOption(key: ValueKey('2'), value: 2),
40+
FormBuilderFieldOption(key: ValueKey('3'), value: 3),
41+
],
42+
itemDecoration:
43+
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
44+
);
45+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
46+
47+
// this is a brittle test knowing how we use container for a border
48+
// there is one container for each option
49+
expect(find.byType(Container), findsExactly(3));
50+
});
51+
3152
testWidgets('FormBuilderCheckboxGroup -- didChange',
3253
(WidgetTester tester) async {
3354
const fieldName = 'cbg1';

test/form_builder_radio_group_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,24 @@ void main() {
2727
expect(formSave(), isTrue);
2828
expect(formValue(widgetName), equals(3));
2929
});
30+
31+
testWidgets('FormBuilderRadioGroup -- decoration',
32+
(WidgetTester tester) async {
33+
const widgetName = 'rg1';
34+
final testWidget = FormBuilderRadioGroup<int>(
35+
name: widgetName,
36+
options: const [
37+
FormBuilderFieldOption(key: ValueKey('1'), value: 1),
38+
FormBuilderFieldOption(key: ValueKey('2'), value: 2),
39+
FormBuilderFieldOption(key: ValueKey('3'), value: 3),
40+
],
41+
itemDecoration:
42+
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
43+
);
44+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
45+
46+
// this is a brittle test knowing how we use container for a border
47+
// there is one container for each option
48+
expect(find.byType(Container), findsExactly(3));
49+
});
3050
}

0 commit comments

Comments
 (0)