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

5
import 'package:flutter/gestures.dart';
6 7 8
import 'package:flutter/widgets.dart';

import 'list_tile.dart';
9
import 'list_tile_theme.dart';
10
import 'material_state.dart';
11
import 'switch.dart';
12
import 'switch_theme.dart';
13
import 'theme.dart';
14
import 'theme_data.dart';
15

16 17
// Examples can assume:
// void setState(VoidCallback fn) { }
18
// bool _isSelected = true;
19

20 21
enum _SwitchListTileType { material, adaptive }

22 23
/// A [ListTile] with a [Switch]. In other words, a switch with a label.
///
24 25
/// {@youtube 560 315 https://www.youtube.com/watch?v=0igIjvtEWNU}
///
26
/// The entire list tile is interactive: tapping anywhere in the tile toggles
27 28 29 30 31 32
/// the switch. Tapping and dragging the [Switch] also triggers the [onChanged]
/// callback.
///
/// To ensure that [onChanged] correctly triggers, the state passed
/// into [value] must be properly managed. This is typically done by invoking
/// [State.setState] in [onChanged] to toggle the state value.
33 34 35 36 37 38 39 40 41
///
/// The [value], [onChanged], [activeColor], [activeThumbImage], and
/// [inactiveThumbImage] properties of this widget are identical to the
/// similarly-named properties on the [Switch] widget.
///
/// The [title], [subtitle], [isThreeLine], and [dense] properties are like
/// those of the same name on [ListTile].
///
/// The [selected] property on this widget is similar to the [ListTile.selected]
42
/// property. This tile's [activeColor] is used for the selected item's text color, or
43
/// the theme's [SwitchThemeData.overlayColor] if [activeColor] is null.
44 45 46 47
///
/// This widget does not coordinate the [selected] state and the
/// [value]; to have the list tile appear selected when the
/// switch button is on, use the same value for both.
48 49
///
/// The switch is shown on the right by default in left-to-right languages (i.e.
50 51
/// in the [ListTile.trailing] slot) which can be changed using [controlAffinity].
/// The [secondary] widget is placed in the [ListTile.leading] slot.
52
///
53 54 55 56 57 58 59 60
/// This widget requires a [Material] widget ancestor in the tree to paint
/// itself on, which is typically provided by the app's [Scaffold].
/// The [tileColor], and [selectedTileColor] are not painted by the
/// [SwitchListTile] itself but by the [Material] widget ancestor. In this
/// case, one can wrap a [Material] widget around the [SwitchListTile], e.g.:
///
/// {@tool snippet}
/// ```dart
61
/// ColoredBox(
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
///   color: Colors.green,
///   child: Material(
///     child: SwitchListTile(
///       tileColor: Colors.red,
///       title: const Text('SwitchListTile with red background'),
///       value: true,
///       onChanged:(bool? value) { },
///     ),
///   ),
/// )
/// ```
/// {@end-tool}
///
/// ## Performance considerations when wrapping [SwitchListTile] with [Material]
///
/// Wrapping a large number of [SwitchListTile]s individually with [Material]s
/// is expensive. Consider only wrapping the [SwitchListTile]s that require it
/// or include a common [Material] ancestor where possible.
///
81 82 83
/// To show the [SwitchListTile] as disabled, pass null as the [onChanged]
/// callback.
///
84
/// {@tool dartpad}
85
/// ![SwitchListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile.png)
86 87 88 89
///
/// This widget shows a switch that, when toggled, changes the state of a [bool]
/// member field called `_lights`.
///
90
/// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.0.dart **
91
/// {@end-tool}
92
///
93 94 95 96 97 98 99
/// {@tool dartpad}
/// This sample demonstrates how [SwitchListTile] positions the switch widget
/// relative to the text in different configurations.
///
/// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.1.dart **
/// {@end-tool}
///
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
/// ## Semantics in SwitchListTile
///
/// Since the entirety of the SwitchListTile is interactive, it should represent
/// itself as a single interactive entity.
///
/// To do so, a SwitchListTile 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, SwitchListTile 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
/// SwitchListTile. [RichText] has an embedded gesture recognizer that
/// requires its own [Semantics] node, which directly conflicts with
/// SwitchListTile'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.
///
117
/// {@tool dartpad}
118 119 120 121 122 123
/// ![Switch list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile_semantics.png)
///
/// Here is an example of a custom labeled radio widget, called
/// LinkedLabelRadio, that includes an interactive [RichText] widget that
/// handles tap gestures.
///
124
/// ** See code in examples/api/lib/material/switch_list_tile/custom_labeled_switch.0.dart **
125 126 127 128 129 130 131 132 133
/// {@end-tool}
///
/// ## SwitchListTile isn't exactly what I want
///
/// If the way SwitchListTile pads and positions its elements isn't quite what
/// you're looking for, you can create custom labeled switch widgets by
/// combining [Switch] with other widgets, such as [Text], [Padding] and
/// [InkWell].
///
134
/// {@tool dartpad}
135 136 137 138 139
/// ![Custom switch list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile_custom.png)
///
/// Here is an example of a custom LabeledSwitch widget, but you can easily
/// make your own configurable widget.
///
140
/// ** See code in examples/api/lib/material/switch_list_tile/custom_labeled_switch.1.dart **
141 142
/// {@end-tool}
///
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
/// See also:
///
///  * [ListTileTheme], which can be used to affect the style of list tiles,
///    including switch list tiles.
///  * [CheckboxListTile], a similar widget for checkboxes.
///  * [RadioListTile], a similar widget for radio buttons.
///  * [ListTile] and [Switch], the widgets from which this widget is made.
class SwitchListTile extends StatelessWidget {
  /// Creates a combination of a list tile and a switch.
  ///
  /// The switch tile itself does not maintain any state. Instead, when the
  /// state of the switch changes, the widget calls the [onChanged] callback.
  /// Most widgets that use a switch will listen for the [onChanged] callback
  /// and rebuild the switch tile with a new [value] to update the visual
  /// appearance of the switch.
  ///
  /// The following arguments are required:
  ///
  /// * [value] determines whether this switch is on or off.
  /// * [onChanged] is called when the user toggles the switch on or off.
  const SwitchListTile({
164
    super.key,
165 166
    required this.value,
    required this.onChanged,
167
    this.activeColor,
168 169 170
    this.activeTrackColor,
    this.inactiveThumbColor,
    this.inactiveTrackColor,
171
    this.activeThumbImage,
172
    this.onActiveThumbImageError,
173
    this.inactiveThumbImage,
174 175 176 177 178 179 180 181 182 183 184 185 186 187
    this.onInactiveThumbImageError,
    this.thumbColor,
    this.trackColor,
    this.trackOutlineColor,
    this.thumbIcon,
    this.materialTapTargetSize,
    this.dragStartBehavior = DragStartBehavior.start,
    this.mouseCursor,
    this.overlayColor,
    this.splashRadius,
    this.focusNode,
    this.onFocusChange,
    this.autofocus = false,
    this.tileColor,
188 189
    this.title,
    this.subtitle,
190
    this.isThreeLine = false,
191
    this.dense,
192
    this.contentPadding,
193
    this.secondary,
194
    this.selected = false,
195
    this.controlAffinity = ListTileControlAffinity.platform,
196
    this.shape,
197
    this.selectedTileColor,
198 199
    this.visualDensity,
    this.enableFeedback,
200
    this.hoverColor,
201
  }) : _switchListTileType = _SwitchListTileType.material,
202 203 204
       applyCupertinoTheme = false,
       assert(activeThumbImage != null || onActiveThumbImageError == null),
       assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
205
       assert(!isThreeLine || subtitle != null);
206

207 208 209
  /// Creates a Material [ListTile] with an adaptive [Switch], following
  /// Material design's
  /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
210
  ///
211 212 213 214
  /// This widget uses [Switch.adaptive] to change the graphics of the switch
  /// component based on the ambient [ThemeData.platform]. On iOS and macOS, a
  /// [CupertinoSwitch] will be used. On other platforms a Material design
  /// [Switch] will be used.
215 216 217
  ///
  /// If a [CupertinoSwitch] is created, the following parameters are
  /// ignored: [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor],
218
  /// [activeThumbImage], [inactiveThumbImage].
219
  const SwitchListTile.adaptive({
220
    super.key,
221 222
    required this.value,
    required this.onChanged,
223 224 225 226 227
    this.activeColor,
    this.activeTrackColor,
    this.inactiveThumbColor,
    this.inactiveTrackColor,
    this.activeThumbImage,
228
    this.onActiveThumbImageError,
229
    this.inactiveThumbImage,
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    this.onInactiveThumbImageError,
    this.thumbColor,
    this.trackColor,
    this.trackOutlineColor,
    this.thumbIcon,
    this.materialTapTargetSize,
    this.dragStartBehavior = DragStartBehavior.start,
    this.mouseCursor,
    this.overlayColor,
    this.splashRadius,
    this.focusNode,
    this.onFocusChange,
    this.autofocus = false,
    this.applyCupertinoTheme,
    this.tileColor,
245 246 247 248
    this.title,
    this.subtitle,
    this.isThreeLine = false,
    this.dense,
249
    this.contentPadding,
250 251
    this.secondary,
    this.selected = false,
252
    this.controlAffinity = ListTileControlAffinity.platform,
253
    this.shape,
254
    this.selectedTileColor,
255 256
    this.visualDensity,
    this.enableFeedback,
257
    this.hoverColor,
258
  }) : _switchListTileType = _SwitchListTileType.adaptive,
259 260 261
       assert(!isThreeLine || subtitle != null),
       assert(activeThumbImage != null || onActiveThumbImageError == null),
       assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

  /// Whether this switch is checked.
  ///
  /// This property must not be null.
  final bool value;

  /// Called when the user toggles the switch on or off.
  ///
  /// The switch passes the new value to the callback but does not actually
  /// change state until the parent widget rebuilds the switch tile with the
  /// new value.
  ///
  /// If null, the switch will be displayed as disabled.
  ///
  /// 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:
  ///
280
  /// {@tool snippet}
281
  /// ```dart
282
  /// SwitchListTile(
283
  ///   value: _isSelected,
284 285
  ///   onChanged: (bool newValue) {
  ///     setState(() {
286
  ///       _isSelected = newValue;
287 288
  ///     });
  ///   },
289
  ///   title: const Text('Selection'),
290
  /// )
291
  /// ```
292
  /// {@end-tool}
293
  final ValueChanged<bool>? onChanged;
294

295
  /// {@macro flutter.material.switch.activeColor}
296
  ///
297
  /// Defaults to [ColorScheme.secondary] of the current [Theme].
298
  final Color? activeColor;
299

300
  /// {@macro flutter.material.switch.activeTrackColor}
301 302
  ///
  /// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%.
303 304
  ///
  /// Ignored if created with [SwitchListTile.adaptive].
305
  final Color? activeTrackColor;
306

307
  /// {@macro flutter.material.switch.inactiveThumbColor}
308 309
  ///
  /// Defaults to the colors described in the Material design specification.
310 311
  ///
  /// Ignored if created with [SwitchListTile.adaptive].
312
  final Color? inactiveThumbColor;
313

314
  /// {@macro flutter.material.switch.inactiveTrackColor}
315 316
  ///
  /// Defaults to the colors described in the Material design specification.
317 318
  ///
  /// Ignored if created with [SwitchListTile.adaptive].
319
  final Color? inactiveTrackColor;
320

321
  /// {@macro flutter.material.switch.activeThumbImage}
322
  final ImageProvider? activeThumbImage;
323

324 325 326 327
  /// {@macro flutter.material.switch.onActiveThumbImageError}
  final ImageErrorListener? onActiveThumbImageError;

  /// {@macro flutter.material.switch.inactiveThumbImage}
328 329
  ///
  /// Ignored if created with [SwitchListTile.adaptive].
330
  final ImageProvider? inactiveThumbImage;
331

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
  /// {@macro flutter.material.switch.onInactiveThumbImageError}
  final ImageErrorListener? onInactiveThumbImageError;

  /// The color of this switch's thumb.
  ///
  /// Resolved in the following states:
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.disabled].
  ///
  /// If null, then the value of [activeColor] is used in the selected state
  /// and [inactiveThumbColor] in the default state. If that is also null, then
  /// the value of [SwitchThemeData.thumbColor] is used. If that is also null,
  /// The default value is used.
  final MaterialStateProperty<Color?>? thumbColor;

  /// The color of this switch's track.
  ///
  /// Resolved in the following states:
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.disabled].
  ///
  /// If null, then the value of [activeTrackColor] is used in the selected
  /// state and [inactiveTrackColor] in the default state. If that is also null,
  /// then the value of [SwitchThemeData.trackColor] is used. If that is also
  /// null, then the default value is used.
  final MaterialStateProperty<Color?>? trackColor;

  /// {@macro flutter.material.switch.trackOutlineColor}
  ///
  /// The [ListTile] will be focused when this [SwitchListTile] requests focus,
  /// so the focused outline color of the switch will be ignored.
  ///
  /// In Material 3, the outline color defaults to transparent in the selected
  /// state and [ColorScheme.outline] in the unselected state. In Material 2,
  /// the [Switch] track has no outline.
  final MaterialStateProperty<Color?>? trackOutlineColor;

  /// The icon to use on the thumb of this switch
  ///
  /// Resolved in the following states:
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.disabled].
  ///
  /// If null, then the value of [SwitchThemeData.thumbIcon] is used. If this is
  /// also null, then the [Switch] does not have any icons on the thumb.
  final MaterialStateProperty<Icon?>? thumbIcon;

  /// {@macro flutter.material.switch.materialTapTargetSize}
  ///
  /// defaults to [MaterialTapTargetSize.shrinkWrap].
  final MaterialTapTargetSize? materialTapTargetSize;

  /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

  /// The cursor for a mouse pointer when it enters or is hovering over the
  /// widget.
  ///
  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
  ///
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.disabled].
  ///
  /// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that
  /// is also null, then [MaterialStateMouseCursor.clickable] is used.
  final MouseCursor? mouseCursor;

  /// The color for the switch's [Material].
  ///
  /// Resolves in the following states:
  ///  * [MaterialState.pressed].
  ///  * [MaterialState.selected].
  ///  * [MaterialState.hovered].
  ///
  /// If null, then the value of [activeColor] with alpha [kRadialReactionAlpha]
  /// and [hoverColor] is used in the pressed and hovered state. If that is also
  /// null, the value of [SwitchThemeData.overlayColor] is used. If that is
  /// also null, then the default value is used in the pressed and hovered state.
  final MaterialStateProperty<Color?>? overlayColor;

  /// {@macro flutter.material.switch.splashRadius}
  ///
  /// If null, then the value of [SwitchThemeData.splashRadius] is used. If that
  /// is also null, then [kRadialReactionRadius] is used.
  final double? splashRadius;

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

  /// {@macro flutter.material.inkwell.onFocusChange}
  final ValueChanged<bool>? onFocusChange;

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

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

435 436 437
  /// The primary content of the list tile.
  ///
  /// Typically a [Text] widget.
438
  final Widget? title;
439 440 441 442

  /// Additional content displayed below the title.
  ///
  /// Typically a [Text] widget.
443
  final Widget? subtitle;
444 445 446 447

  /// A widget to display on the opposite side of the tile from the switch.
  ///
  /// Typically an [Icon] widget.
448
  final Widget? secondary;
449 450 451 452 453 454 455 456 457

  /// 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.
  ///
458
  /// If this property is null then its value is based on [ListTileThemeData.dense].
459
  final bool? dense;
460

461 462 463 464 465 466 467
  /// The tile's internal padding.
  ///
  /// Insets a [SwitchListTile]'s contents: its [title], [subtitle],
  /// [secondary], and [Switch] widgets.
  ///
  /// If null, [ListTile]'s default of `EdgeInsets.symmetric(horizontal: 16.0)`
  /// is used.
468
  final EdgeInsetsGeometry? contentPadding;
469

470 471 472 473 474 475 476 477 478
  /// 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 switch is
  /// on, pass the same value to both.
  ///
  /// Normally, this property is left to its default value, false.
  final bool selected;

479 480 481
  /// If adaptive, creates the switch with [Switch.adaptive].
  final _SwitchListTileType _switchListTileType;

482 483
  /// Defines the position of control and [secondary], relative to text.
  ///
484
  /// By default, the value of [controlAffinity] is [ListTileControlAffinity.platform].
485 486
  final ListTileControlAffinity controlAffinity;

487
  /// {@macro flutter.material.ListTile.shape}
488 489
  final ShapeBorder? shape;

490 491 492
  /// If non-null, defines the background color when [SwitchListTile.selected] is true.
  final Color? selectedTileColor;

493 494 495 496 497 498 499 500 501 502 503 504
  /// Defines how compact the list tile's layout will be.
  ///
  /// {@macro flutter.material.themedata.visualDensity}
  final VisualDensity? visualDensity;

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

505 506 507
  /// The color for the tile's [Material] when a pointer is hovering over it.
  final Color? hoverColor;

508 509
  /// {@macro flutter.cupertino.CupertinoSwitch.applyTheme}
  final bool? applyCupertinoTheme;
510

511 512
  @override
  Widget build(BuildContext context) {
513
    final Widget control;
514 515 516 517 518 519 520 521
    switch (_switchListTileType) {
      case _SwitchListTileType.adaptive:
        control = Switch.adaptive(
          value: value,
          onChanged: onChanged,
          activeColor: activeColor,
          activeThumbImage: activeThumbImage,
          inactiveThumbImage: inactiveThumbImage,
522
          materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
523 524 525
          activeTrackColor: activeTrackColor,
          inactiveTrackColor: inactiveTrackColor,
          inactiveThumbColor: inactiveThumbColor,
526
          autofocus: autofocus,
527
          onFocusChange: onFocusChange,
528 529 530 531
          onActiveThumbImageError: onActiveThumbImageError,
          onInactiveThumbImageError: onInactiveThumbImageError,
          thumbColor: thumbColor,
          trackColor: trackColor,
532
          trackOutlineColor: trackOutlineColor,
533 534 535 536 537 538
          thumbIcon: thumbIcon,
          applyCupertinoTheme: applyCupertinoTheme,
          dragStartBehavior: dragStartBehavior,
          mouseCursor: mouseCursor,
          splashRadius: splashRadius,
          overlayColor: overlayColor,
539 540 541 542 543 544 545 546 547
        );

      case _SwitchListTileType.material:
        control = Switch(
          value: value,
          onChanged: onChanged,
          activeColor: activeColor,
          activeThumbImage: activeThumbImage,
          inactiveThumbImage: inactiveThumbImage,
548
          materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
549 550 551
          activeTrackColor: activeTrackColor,
          inactiveTrackColor: inactiveTrackColor,
          inactiveThumbColor: inactiveThumbColor,
552
          autofocus: autofocus,
553
          onFocusChange: onFocusChange,
554 555 556 557
          onActiveThumbImageError: onActiveThumbImageError,
          onInactiveThumbImageError: onInactiveThumbImageError,
          thumbColor: thumbColor,
          trackColor: trackColor,
558
          trackOutlineColor: trackOutlineColor,
559 560 561 562 563
          thumbIcon: thumbIcon,
          dragStartBehavior: dragStartBehavior,
          mouseCursor: mouseCursor,
          splashRadius: splashRadius,
          overlayColor: overlayColor,
564 565
        );
    }
566

567
    Widget? leading, trailing;
568 569 570 571 572 573 574 575 576 577
    switch (controlAffinity) {
      case ListTileControlAffinity.leading:
        leading = control;
        trailing = secondary;
      case ListTileControlAffinity.trailing:
      case ListTileControlAffinity.platform:
        leading = secondary;
        trailing = control;
    }

578 579 580 581 582 583 584 585
    final ThemeData theme = Theme.of(context);
    final SwitchThemeData switchTheme = SwitchTheme.of(context);
    final Set<MaterialState> states = <MaterialState>{
      if (selected) MaterialState.selected,
    };
    final Color effectiveActiveColor = activeColor
      ?? switchTheme.thumbColor?.resolve(states)
      ?? theme.colorScheme.secondary;
586
    return MergeSemantics(
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
      child: ListTile(
        selectedColor: effectiveActiveColor,
        leading: leading,
        title: title,
        subtitle: subtitle,
        trailing: trailing,
        isThreeLine: isThreeLine,
        dense: dense,
        contentPadding: contentPadding,
        enabled: onChanged != null,
        onTap: onChanged != null ? () { onChanged!(!value); } : null,
        selected: selected,
        selectedTileColor: selectedTileColor,
        autofocus: autofocus,
        shape: shape,
        tileColor: tileColor,
        visualDensity: visualDensity,
        focusNode: focusNode,
605
        onFocusChange: onFocusChange,
606 607
        enableFeedback: enableFeedback,
        hoverColor: hoverColor,
608 609 610 611
      ),
    );
  }
}