1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'action_chip.dart';
6/// @docImport 'checkbox.dart';
7/// @docImport 'choice_chip.dart';
8/// @docImport 'circle_avatar.dart';
9/// @docImport 'input_chip.dart';
10/// @docImport 'material.dart';
11/// @docImport 'switch.dart';
12library;
13
14import 'package:flutter/foundation.dart' show clampDouble;
15import 'package:flutter/widgets.dart';
16
17import 'chip.dart';
18import 'chip_theme.dart';
19import 'color_scheme.dart';
20import 'colors.dart';
21import 'debug.dart';
22import 'icons.dart';
23import 'material_state.dart';
24import 'text_theme.dart';
25import 'theme.dart';
26import 'theme_data.dart';
27
28enum _ChipVariant { flat, elevated }
29
30/// A Material Design filter chip.
31///
32/// Filter chips use tags or descriptive words as a way to filter content.
33///
34/// Filter chips are a good alternative to [Checkbox] or [Switch] widgets.
35/// Unlike these alternatives, filter chips allow for clearly delineated and
36/// exposed options in a compact area.
37///
38/// Requires one of its ancestors to be a [Material] widget.
39///
40/// {@tool dartpad}
41/// This example shows how to use [FilterChip]s to filter through exercises.
42///
43/// ** See code in examples/api/lib/material/filter_chip/filter_chip.0.dart **
44/// {@end-tool}
45///
46/// ## Material Design 3
47///
48/// [FilterChip] can be used for multiple select Filter chip from
49/// Material Design 3. If [ThemeData.useMaterial3] is true, then [FilterChip]
50/// will be styled to match the Material Design 3 specification for Filter
51/// chips. Use [ChoiceChip] for single select Filter chips.
52///
53/// See also:
54///
55/// * [Chip], a chip that displays information and can be deleted.
56/// * [InputChip], a chip that represents a complex piece of information, such
57/// as an entity (person, place, or thing) or conversational text, in a
58/// compact form.
59/// * [ChoiceChip], allows a single selection from a set of options. Choice
60/// chips contain related descriptive text or categories.
61/// * [ActionChip], represents an action related to primary content.
62/// * [CircleAvatar], which shows images or initials of people.
63/// * [Wrap], A widget that displays its children in multiple horizontal or
64/// vertical runs.
65/// * <https://material.io/design/components/chips.html>
66class FilterChip extends StatelessWidget
67 implements
68 ChipAttributes,
69 DeletableChipAttributes,
70 SelectableChipAttributes,
71 CheckmarkableChipAttributes,
72 DisabledChipAttributes {
73 /// Create a chip that acts like a checkbox.
74 ///
75 /// The [selected], [label], [autofocus], and [clipBehavior] arguments must
76 /// not be null. When [onSelected] is null, the [FilterChip] will be disabled.
77 /// The [pressElevation] and [elevation] must be null or non-negative. Typically,
78 /// [pressElevation] is greater than [elevation].
79 const FilterChip({
80 super.key,
81 this.avatar,
82 required this.label,
83 this.labelStyle,
84 this.labelPadding,
85 this.selected = false,
86 required this.onSelected,
87 this.deleteIcon,
88 this.onDeleted,
89 this.deleteIconColor,
90 this.deleteButtonTooltipMessage,
91 this.pressElevation,
92 this.disabledColor,
93 this.selectedColor,
94 this.tooltip,
95 this.side,
96 this.shape,
97 this.clipBehavior = Clip.none,
98 this.focusNode,
99 this.autofocus = false,
100 this.color,
101 this.backgroundColor,
102 this.padding,
103 this.visualDensity,
104 this.materialTapTargetSize,
105 this.elevation,
106 this.shadowColor,
107 this.surfaceTintColor,
108 this.iconTheme,
109 this.selectedShadowColor,
110 this.showCheckmark,
111 this.checkmarkColor,
112 this.avatarBorder = const CircleBorder(),
113 this.avatarBoxConstraints,
114 this.deleteIconBoxConstraints,
115 this.chipAnimationStyle,
116 this.mouseCursor,
117 }) : assert(pressElevation == null || pressElevation >= 0.0),
118 assert(elevation == null || elevation >= 0.0),
119 _chipVariant = _ChipVariant.flat;
120
121 /// Create an elevated chip that acts like a checkbox.
122 ///
123 /// The [selected], [label], [autofocus], and [clipBehavior] arguments must
124 /// not be null. When [onSelected] is null, the [FilterChip] will be disabled.
125 /// The [pressElevation] and [elevation] must be null or non-negative. Typically,
126 /// [pressElevation] is greater than [elevation].
127 const FilterChip.elevated({
128 super.key,
129 this.avatar,
130 required this.label,
131 this.labelStyle,
132 this.labelPadding,
133 this.selected = false,
134 required this.onSelected,
135 this.deleteIcon,
136 this.onDeleted,
137 this.deleteIconColor,
138 this.deleteButtonTooltipMessage,
139 this.pressElevation,
140 this.disabledColor,
141 this.selectedColor,
142 this.tooltip,
143 this.side,
144 this.shape,
145 this.clipBehavior = Clip.none,
146 this.focusNode,
147 this.autofocus = false,
148 this.color,
149 this.backgroundColor,
150 this.padding,
151 this.visualDensity,
152 this.materialTapTargetSize,
153 this.elevation,
154 this.shadowColor,
155 this.surfaceTintColor,
156 this.iconTheme,
157 this.selectedShadowColor,
158 this.showCheckmark,
159 this.checkmarkColor,
160 this.avatarBorder = const CircleBorder(),
161 this.avatarBoxConstraints,
162 this.deleteIconBoxConstraints,
163 this.chipAnimationStyle,
164 this.mouseCursor,
165 }) : assert(pressElevation == null || pressElevation >= 0.0),
166 assert(elevation == null || elevation >= 0.0),
167 _chipVariant = _ChipVariant.elevated;
168
169 @override
170 final Widget? avatar;
171 @override
172 final Widget label;
173 @override
174 final TextStyle? labelStyle;
175 @override
176 final EdgeInsetsGeometry? labelPadding;
177 @override
178 final bool selected;
179 @override
180 final ValueChanged<bool>? onSelected;
181 @override
182 final Widget? deleteIcon;
183 @override
184 final VoidCallback? onDeleted;
185 @override
186 final Color? deleteIconColor;
187 @override
188 final String? deleteButtonTooltipMessage;
189 @override
190 final double? pressElevation;
191 @override
192 final Color? disabledColor;
193 @override
194 final Color? selectedColor;
195 @override
196 final String? tooltip;
197 @override
198 final BorderSide? side;
199 @override
200 final OutlinedBorder? shape;
201 @override
202 final Clip clipBehavior;
203 @override
204 final FocusNode? focusNode;
205 @override
206 final bool autofocus;
207 @override
208 final MaterialStateProperty<Color?>? color;
209 @override
210 final Color? backgroundColor;
211 @override
212 final EdgeInsetsGeometry? padding;
213 @override
214 final VisualDensity? visualDensity;
215 @override
216 final MaterialTapTargetSize? materialTapTargetSize;
217 @override
218 final double? elevation;
219 @override
220 final Color? shadowColor;
221 @override
222 final Color? surfaceTintColor;
223 @override
224 final Color? selectedShadowColor;
225 @override
226 final bool? showCheckmark;
227 @override
228 final Color? checkmarkColor;
229 @override
230 final ShapeBorder avatarBorder;
231 @override
232 final IconThemeData? iconTheme;
233 @override
234 final BoxConstraints? avatarBoxConstraints;
235 @override
236 final BoxConstraints? deleteIconBoxConstraints;
237 @override
238 final ChipAnimationStyle? chipAnimationStyle;
239 @override
240 final MouseCursor? mouseCursor;
241
242 @override
243 bool get isEnabled => onSelected != null;
244
245 final _ChipVariant _chipVariant;
246
247 @override
248 Widget build(BuildContext context) {
249 assert(debugCheckHasMaterial(context));
250 final ChipThemeData? defaults =
251 Theme.of(context).useMaterial3
252 ? _FilterChipDefaultsM3(context, isEnabled, selected, _chipVariant)
253 : null;
254 final Widget? resolvedDeleteIcon =
255 deleteIcon ?? (Theme.of(context).useMaterial3 ? const Icon(Icons.clear, size: 18) : null);
256 return RawChip(
257 defaultProperties: defaults,
258 avatar: avatar,
259 label: label,
260 labelStyle: labelStyle,
261 labelPadding: labelPadding,
262 onSelected: onSelected,
263 deleteIcon: resolvedDeleteIcon,
264 onDeleted: onDeleted,
265 deleteIconColor: deleteIconColor,
266 deleteButtonTooltipMessage: deleteButtonTooltipMessage,
267 pressElevation: pressElevation,
268 selected: selected,
269 tooltip: tooltip,
270 side: side,
271 shape: shape,
272 clipBehavior: clipBehavior,
273 focusNode: focusNode,
274 autofocus: autofocus,
275 color: color,
276 backgroundColor: backgroundColor,
277 disabledColor: disabledColor,
278 selectedColor: selectedColor,
279 padding: padding,
280 visualDensity: visualDensity,
281 isEnabled: isEnabled,
282 materialTapTargetSize: materialTapTargetSize,
283 elevation: elevation,
284 shadowColor: shadowColor,
285 surfaceTintColor: surfaceTintColor,
286 selectedShadowColor: selectedShadowColor,
287 showCheckmark: showCheckmark,
288 checkmarkColor: checkmarkColor,
289 avatarBorder: avatarBorder,
290 iconTheme: iconTheme,
291 avatarBoxConstraints: avatarBoxConstraints,
292 deleteIconBoxConstraints: deleteIconBoxConstraints,
293 chipAnimationStyle: chipAnimationStyle,
294 mouseCursor: mouseCursor,
295 );
296 }
297}
298
299// BEGIN GENERATED TOKEN PROPERTIES - FilterChip
300
301// Do not edit by hand. The code between the "BEGIN GENERATED" and
302// "END GENERATED" comments are generated from data in the Material
303// Design token database by the script:
304// dev/tools/gen_defaults/bin/gen_defaults.dart.
305
306// dart format off
307class _FilterChipDefaultsM3 extends ChipThemeData {
308 _FilterChipDefaultsM3(
309 this.context,
310 this.isEnabled,
311 this.isSelected,
312 this._chipVariant,
313 ) : super(
314 shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
315 showCheckmark: true,
316 );
317
318 final BuildContext context;
319 final bool isEnabled;
320 final bool isSelected;
321 final _ChipVariant _chipVariant;
322 late final ColorScheme _colors = Theme.of(context).colorScheme;
323 late final TextTheme _textTheme = Theme.of(context).textTheme;
324
325 @override
326 double? get elevation => _chipVariant == _ChipVariant.flat
327 ? 0.0
328 : isEnabled ? 1.0 : 0.0;
329
330 @override
331 double? get pressElevation => 1.0;
332
333 @override
334 TextStyle? get labelStyle => _textTheme.labelLarge?.copyWith(
335 color: isEnabled
336 ? isSelected
337 ? _colors.onSecondaryContainer
338 : _colors.onSurfaceVariant
339 : _colors.onSurface,
340 );
341
342 @override
343 MaterialStateProperty<Color?>? get color =>
344 MaterialStateProperty.resolveWith((Set<MaterialState> states) {
345 if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) {
346 return _chipVariant == _ChipVariant.flat
347 ? _colors.onSurface.withOpacity(0.12)
348 : _colors.onSurface.withOpacity(0.12);
349 }
350 if (states.contains(MaterialState.disabled)) {
351 return _chipVariant == _ChipVariant.flat
352 ? null
353 : _colors.onSurface.withOpacity(0.12);
354 }
355 if (states.contains(MaterialState.selected)) {
356 return _chipVariant == _ChipVariant.flat
357 ? _colors.secondaryContainer
358 : _colors.secondaryContainer;
359 }
360 return _chipVariant == _ChipVariant.flat
361 ? null
362 : _colors.surfaceContainerLow;
363 });
364
365 @override
366 Color? get shadowColor => _chipVariant == _ChipVariant.flat
367 ? Colors.transparent
368 : _colors.shadow;
369
370 @override
371 Color? get surfaceTintColor => Colors.transparent;
372
373 @override
374 Color? get checkmarkColor => isEnabled
375 ? isSelected
376 ? _colors.onSecondaryContainer
377 : _colors.primary
378 : _colors.onSurface;
379
380 @override
381 Color? get deleteIconColor => isEnabled
382 ? isSelected
383 ? _colors.onSecondaryContainer
384 : _colors.onSurfaceVariant
385 : _colors.onSurface;
386
387 @override
388 BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected
389 ? isEnabled
390 ? BorderSide(color: _colors.outlineVariant)
391 : BorderSide(color: _colors.onSurface.withOpacity(0.12))
392 : const BorderSide(color: Colors.transparent);
393
394 @override
395 IconThemeData? get iconTheme => IconThemeData(
396 color: isEnabled
397 ? isSelected
398 ? _colors.onSecondaryContainer
399 : _colors.primary
400 : _colors.onSurface,
401 size: 18.0,
402 );
403
404 @override
405 EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0);
406
407 /// The label padding of the chip scales with the font size specified in the
408 /// [labelStyle], and the system font size settings that scale font sizes
409 /// globally.
410 ///
411 /// The chip at effective font size 14.0 starts with 8px on each side and as
412 /// the font size scales up to closer to 28.0, the label padding is linearly
413 /// interpolated from 8px to 4px. Once the label has a font size of 2 or
414 /// higher, label padding remains 4px.
415 @override
416 EdgeInsetsGeometry? get labelPadding {
417 final double fontSize = labelStyle?.fontSize ?? 14.0;
418 final double fontSizeRatio = MediaQuery.textScalerOf(context).scale(fontSize) / 14.0;
419 return EdgeInsets.lerp(
420 const EdgeInsets.symmetric(horizontal: 8.0),
421 const EdgeInsets.symmetric(horizontal: 4.0),
422 clampDouble(fontSizeRatio - 1.0, 0.0, 1.0),
423 )!;
424 }
425}
426// dart format on
427
428// END GENERATED TOKEN PROPERTIES - FilterChip
429

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com