checkbox_list_tile.dart 12.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';

import 'checkbox.dart';
import 'list_tile.dart';
9
import 'list_tile_theme.dart';
10
import 'theme.dart';
11
import 'theme_data.dart';
12

13
// Examples can assume:
14
// bool? _throwShotAway = false;
15 16
// void setState(VoidCallback fn) { }

17 18 19 20 21
/// A [ListTile] with a [Checkbox]. In other words, a checkbox with a label.
///
/// The entire list tile is interactive: tapping anywhere in the tile toggles
/// the checkbox.
///
22 23
/// {@youtube 560 315 https://www.youtube.com/watch?v=RkSqPAn9szs}
///
24
/// The [value], [onChanged], [activeColor] and [checkColor] properties of this widget are
25 26
/// identical to the similarly-named properties on the [Checkbox] widget.
///
27
/// The [title], [subtitle], [isThreeLine], [dense], and [contentPadding] properties are like
28 29 30
/// those of the same name on [ListTile].
///
/// The [selected] property on this widget is similar to the [ListTile.selected]
31 32 33 34
/// property. This tile's [activeColor] is used for the selected item's text color, or
/// the theme's [ThemeData.toggleableActiveColor] if [activeColor] is null.
///
/// This widget does not coordinate the [selected] state and the [value] state; to have the list tile
35
/// appear selected when the checkbox is checked, pass the same value to both.
36 37 38 39 40 41
///
/// The checkbox is shown on the right by default in left-to-right languages
/// (i.e. the trailing edge). This can be changed using [controlAffinity]. The
/// [secondary] widget is placed on the opposite side. This maps to the
/// [ListTile.leading] and [ListTile.trailing] properties of [ListTile].
///
42 43 44
/// To show the [CheckboxListTile] as disabled, pass null as the [onChanged]
/// callback.
///
45
/// {@tool dartpad}
46
/// ![CheckboxListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile.png)
47 48 49 50
///
/// This widget shows a checkbox that, when checked, slows down all animations
/// (including the animation of the checkbox itself getting checked!).
///
51 52 53
/// This sample requires that you also import 'package:flutter/scheduler.dart',
/// so that you can reference [timeDilation].
///
54
/// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.0.dart **
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
/// {@end-tool}
///
/// ## Semantics in CheckboxListTile
///
/// Since the entirety of the CheckboxListTile is interactive, it should represent
/// itself as a single interactive entity.
///
/// To do so, a CheckboxListTile widget wraps its children with a [MergeSemantics]
/// widget. [MergeSemantics] will attempt to merge its descendant [Semantics]
/// nodes into one node in the semantics tree. Therefore, CheckboxListTile will
/// throw an error if any of its children requires its own [Semantics] node.
///
/// For example, you cannot nest a [RichText] widget as a descendant of
/// CheckboxListTile. [RichText] has an embedded gesture recognizer that
/// requires its own [Semantics] node, which directly conflicts with
/// CheckboxListTile's desire to merge all its descendants' semantic nodes
/// into one. Therefore, it may be necessary to create a custom radio tile
/// widget to accommodate similar use cases.
///
74
/// {@tool dartpad}
75 76 77 78 79 80
/// ![Checkbox list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile_semantics.png)
///
/// Here is an example of a custom labeled checkbox widget, called
/// LinkedLabelCheckbox, that includes an interactive [RichText] widget that
/// handles tap gestures.
///
81
/// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.1.dart **
82 83 84 85 86 87 88 89 90
/// {@end-tool}
///
/// ## CheckboxListTile isn't exactly what I want
///
/// If the way CheckboxListTile pads and positions its elements isn't quite
/// what you're looking for, you can create custom labeled checkbox widgets by
/// combining [Checkbox] with other widgets, such as [Text], [Padding] and
/// [InkWell].
///
91
/// {@tool dartpad}
92 93 94 95 96
/// ![Custom checkbox list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile_custom.png)
///
/// Here is an example of a custom LabeledCheckbox widget, but you can easily
/// make your own configurable widget.
///
97
/// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.2.dart **
98
/// {@end-tool}
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
///
/// See also:
///
///  * [ListTileTheme], which can be used to affect the style of list tiles,
///    including checkbox list tiles.
///  * [RadioListTile], a similar widget for radio buttons.
///  * [SwitchListTile], a similar widget for switches.
///  * [ListTile] and [Checkbox], the widgets from which this widget is made.
class CheckboxListTile extends StatelessWidget {
  /// Creates a combination of a list tile and a checkbox.
  ///
  /// The checkbox tile itself does not maintain any state. Instead, when the
  /// state of the checkbox changes, the widget calls the [onChanged] callback.
  /// Most widgets that use a checkbox will listen for the [onChanged] callback
  /// and rebuild the checkbox tile with a new [value] to update the visual
  /// appearance of the checkbox.
  ///
  /// The following arguments are required:
  ///
118 119
  /// * [value], which determines whether the checkbox is checked. The [value]
  ///   can only be null if [tristate] is true.
120 121
  /// * [onChanged], which is called when the value of the checkbox should
  ///   change. It can be set to null to disable the checkbox.
122 123
  ///
  /// The value of [tristate] must not be null.
124
  const CheckboxListTile({
125
    super.key,
126 127
    required this.value,
    required this.onChanged,
128
    this.activeColor,
129
    this.checkColor,
130
    this.enabled,
131
    this.tileColor,
132 133
    this.title,
    this.subtitle,
134
    this.isThreeLine = false,
135 136
    this.dense,
    this.secondary,
137 138
    this.selected = false,
    this.controlAffinity = ListTileControlAffinity.platform,
139
    this.autofocus = false,
140
    this.contentPadding,
141
    this.tristate = false,
142
    this.shape,
143
    this.checkboxShape,
144
    this.selectedTileColor,
145
    this.side,
146 147 148
    this.visualDensity,
    this.focusNode,
    this.enableFeedback,
149 150
  }) : assert(tristate != null),
       assert(tristate || value != null),
151 152 153 154
       assert(isThreeLine != null),
       assert(!isThreeLine || subtitle != null),
       assert(selected != null),
       assert(controlAffinity != null),
155
       assert(autofocus != null);
156 157

  /// Whether this checkbox is checked.
158
  final bool? value;
159 160 161 162 163 164 165 166 167

  /// Called when the value of the checkbox should change.
  ///
  /// The checkbox passes the new value to the callback but does not actually
  /// change state until the parent widget rebuilds the checkbox tile with the
  /// new value.
  ///
  /// If null, the checkbox will be displayed as disabled.
  ///
168 169
  /// {@tool snippet}
  ///
170 171 172 173 174
  /// The callback provided to [onChanged] should update the state of the parent
  /// [StatefulWidget] using the [State.setState] method, so that the parent
  /// gets rebuilt; for example:
  ///
  /// ```dart
175
  /// CheckboxListTile(
176
  ///   value: _throwShotAway,
177
  ///   onChanged: (bool? newValue) {
178 179 180 181
  ///     setState(() {
  ///       _throwShotAway = newValue;
  ///     });
  ///   },
182
  ///   title: const Text('Throw away your shot'),
183
  /// )
184
  /// ```
185
  /// {@end-tool}
186
  final ValueChanged<bool?>? onChanged;
187 188 189

  /// The color to use when this checkbox is checked.
  ///
190
  /// Defaults to accent color of the current [Theme].
191
  final Color? activeColor;
192

193 194 195
  /// The color to use for the check icon when this checkbox is checked.
  ///
  /// Defaults to Color(0xFFFFFFFF).
196
  final Color? checkColor;
197

198 199 200
  /// {@macro flutter.material.ListTile.tileColor}
  final Color? tileColor;

201 202 203
  /// The primary content of the list tile.
  ///
  /// Typically a [Text] widget.
204
  final Widget? title;
205 206 207 208

  /// Additional content displayed below the title.
  ///
  /// Typically a [Text] widget.
209
  final Widget? subtitle;
210 211 212 213

  /// A widget to display on the opposite side of the tile from the checkbox.
  ///
  /// Typically an [Icon] widget.
214
  final Widget? secondary;
215 216 217 218 219 220 221 222 223

  /// Whether this list tile is intended to display three lines of text.
  ///
  /// If false, the list tile is treated as having one line if the subtitle is
  /// null and treated as having two lines if the subtitle is non-null.
  final bool isThreeLine;

  /// Whether this list tile is part of a vertically dense list.
  ///
224
  /// If this property is null then its value is based on [ListTileThemeData.dense].
225
  final bool? dense;
226 227 228 229 230 231 232 233 234 235 236 237 238

  /// Whether to render icons and text in the [activeColor].
  ///
  /// No effort is made to automatically coordinate the [selected] state and the
  /// [value] state. To have the list tile appear selected when the checkbox is
  /// checked, pass the same value to both.
  ///
  /// Normally, this property is left to its default value, false.
  final bool selected;

  /// Where to place the control relative to the text.
  final ListTileControlAffinity controlAffinity;

239 240 241
  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

242 243 244 245 246 247
  /// Defines insets surrounding the tile's contents.
  ///
  /// This value will surround the [Checkbox], [title], [subtitle], and [secondary]
  /// widgets in [CheckboxListTile].
  ///
  /// When the value is null, the `contentPadding` is `EdgeInsets.symmetric(horizontal: 16.0)`.
248
  final EdgeInsetsGeometry? contentPadding;
249

250 251 252 253 254 255 256 257 258 259 260 261
  /// If true the checkbox's [value] can be true, false, or null.
  ///
  /// Checkbox displays a dash when its value is null.
  ///
  /// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
  /// callback will be applied to true if the current value is false, to null if
  /// value is true, and to false if value is null (i.e. it cycles through false
  /// => true => null => false when tapped).
  ///
  /// If tristate is false (the default), [value] must not be null.
  final bool tristate;

262
  /// {@macro flutter.material.ListTile.shape}
263 264
  final ShapeBorder? shape;

265 266 267 268 269 270 271
  /// {@macro flutter.material.checkbox.shape}
  ///
  /// If this property is null then [CheckboxThemeData.shape] of [ThemeData.checkboxTheme]
  /// is used. If that's null then the shape will be a [RoundedRectangleBorder]
  /// with a circular corner radius of 1.0.
  final OutlinedBorder? checkboxShape;

272 273 274
  /// If non-null, defines the background color when [CheckboxListTile.selected] is true.
  final Color? selectedTileColor;

275 276 277 278 279 280 281 282 283
  /// {@macro flutter.material.checkbox.side}
  ///
  /// The given value is passed directly to [Checkbox.side].
  ///
  /// If this property is null, then [CheckboxThemeData.side] of
  /// [ThemeData.checkboxTheme] is used. If that is also null, then the side
  /// will be width 2.
  final BorderSide? side;

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  /// Defines how compact the list tile's layout will be.
  ///
  /// {@macro flutter.material.themedata.visualDensity}
  final VisualDensity? visualDensity;

  /// {@macro flutter.widgets.Focus.focusNode}
  final FocusNode? focusNode;

  /// {@macro flutter.material.ListTile.enableFeedback}
  ///
  /// See also:
  ///
  ///  * [Feedback] for providing platform-specific feedback to certain actions.
  final bool? enableFeedback;

299 300 301 302 303 304 305
  /// Whether the CheckboxListTile is interactive.
  ///
  /// If false, this list tile is styled with the disabled color from the
  /// current [Theme] and the [ListTile.onTap] callback is
  /// inoperative.
  final bool? enabled;

306 307 308 309
  void _handleValueChange() {
    assert(onChanged != null);
    switch (value) {
      case false:
310
        onChanged!(true);
311 312
        break;
      case true:
313
        onChanged!(tristate ? null : false);
314
        break;
315 316
      case null:
        onChanged!(false);
317 318 319 320
        break;
    }
  }

321 322
  @override
  Widget build(BuildContext context) {
323
    final Widget control = Checkbox(
324
      value: value,
325
      onChanged: enabled ?? true ? onChanged : null ,
326
      activeColor: activeColor,
327
      checkColor: checkColor,
328
      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
329
      autofocus: autofocus,
330
      tristate: tristate,
331
      shape: checkboxShape,
332
      side: side,
333
    );
334
    Widget? leading, trailing;
335 336 337 338 339 340 341 342 343 344 345
    switch (controlAffinity) {
      case ListTileControlAffinity.leading:
        leading = control;
        trailing = secondary;
        break;
      case ListTileControlAffinity.trailing:
      case ListTileControlAffinity.platform:
        leading = secondary;
        trailing = control;
        break;
    }
346
    return MergeSemantics(
347
      child: ListTileTheme.merge(
348
        selectedColor: activeColor ?? Theme.of(context).toggleableActiveColor,
349 350 351 352 353 354 355
        child: ListTile(
          leading: leading,
          title: title,
          subtitle: subtitle,
          trailing: trailing,
          isThreeLine: isThreeLine,
          dense: dense,
356
          enabled: enabled ?? onChanged != null,
357 358 359 360 361 362 363
          onTap: onChanged != null ? _handleValueChange : null,
          selected: selected,
          autofocus: autofocus,
          contentPadding: contentPadding,
          shape: shape,
          selectedTileColor: selectedTileColor,
          tileColor: tileColor,
364 365 366
          visualDensity: visualDensity,
          focusNode: focusNode,
          enableFeedback: enableFeedback,
367
        ),
368 369 370 371
      ),
    );
  }
}