"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/radio_list_tile.dart" (13 Nov 2020, 17193 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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 // @dart = 2.8
    6 
    7 import 'package:flutter/widgets.dart';
    8 
    9 import 'list_tile.dart';
   10 import 'radio.dart';
   11 import 'theme.dart';
   12 import 'theme_data.dart';
   13 
   14 // Examples can assume:
   15 // void setState(VoidCallback fn) { }
   16 
   17 /// A [ListTile] with a [Radio]. In other words, a radio button with a label.
   18 ///
   19 /// The entire list tile is interactive: tapping anywhere in the tile selects
   20 /// the radio button.
   21 ///
   22 /// The [value], [groupValue], [onChanged], and [activeColor] properties of this
   23 /// widget are identical to the similarly-named properties on the [Radio]
   24 /// widget. The type parameter `T` serves the same purpose as that of the
   25 /// [Radio] class' type parameter.
   26 ///
   27 /// The [title], [subtitle], [isThreeLine], and [dense] properties are like
   28 /// those of the same name on [ListTile].
   29 ///
   30 /// The [selected] property on this widget is similar to the [ListTile.selected]
   31 /// property, but the color used is that described by [activeColor], if any,
   32 /// defaulting to the accent color of the current [Theme]. No effort is made to
   33 /// coordinate the [selected] state and the [checked] state; to have the list
   34 /// tile appear selected when the radio button is the selected radio button, set
   35 /// [selected] to true when [value] matches [groupValue].
   36 ///
   37 /// The radio button is shown on the left by default in left-to-right languages
   38 /// (i.e. the leading edge). This can be changed using [controlAffinity]. The
   39 /// [secondary] widget is placed on the opposite side. This maps to the
   40 /// [ListTile.leading] and [ListTile.trailing] properties of [ListTile].
   41 ///
   42 /// To show the [RadioListTile] as disabled, pass null as the [onChanged]
   43 /// callback.
   44 ///
   45 /// {@tool dartpad --template=stateful_widget_scaffold}
   46 ///
   47 /// ![RadioListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/radio_list_tile.png)
   48 ///
   49 /// This widget shows a pair of radio buttons that control the `_character`
   50 /// field. The field is of the type `SingingCharacter`, an enum.
   51 ///
   52 /// ```dart preamble
   53 /// enum SingingCharacter { lafayette, jefferson }
   54 /// ```
   55 /// ```dart
   56 /// SingingCharacter _character = SingingCharacter.lafayette;
   57 ///
   58 /// @override
   59 /// Widget build(BuildContext context) {
   60 ///   return Column(
   61 ///     children: <Widget>[
   62 ///       RadioListTile<SingingCharacter>(
   63 ///         title: const Text('Lafayette'),
   64 ///         value: SingingCharacter.lafayette,
   65 ///         groupValue: _character,
   66 ///         onChanged: (SingingCharacter value) { setState(() { _character = value; }); },
   67 ///       ),
   68 ///       RadioListTile<SingingCharacter>(
   69 ///         title: const Text('Thomas Jefferson'),
   70 ///         value: SingingCharacter.jefferson,
   71 ///         groupValue: _character,
   72 ///         onChanged: (SingingCharacter value) { setState(() { _character = value; }); },
   73 ///       ),
   74 ///     ],
   75 ///   );
   76 /// }
   77 /// ```
   78 /// {@end-tool}
   79 ///
   80 /// ## Semantics in RadioListTile
   81 ///
   82 /// Since the entirety of the RadioListTile is interactive, it should represent
   83 /// itself as a single interactive entity.
   84 ///
   85 /// To do so, a RadioListTile widget wraps its children with a [MergeSemantics]
   86 /// widget. [MergeSemantics] will attempt to merge its descendant [Semantics]
   87 /// nodes into one node in the semantics tree. Therefore, RadioListTile will
   88 /// throw an error if any of its children requires its own [Semantics] node.
   89 ///
   90 /// For example, you cannot nest a [RichText] widget as a descendant of
   91 /// RadioListTile. [RichText] has an embedded gesture recognizer that
   92 /// requires its own [Semantics] node, which directly conflicts with
   93 /// RadioListTile's desire to merge all its descendants' semantic nodes
   94 /// into one. Therefore, it may be necessary to create a custom radio tile
   95 /// widget to accommodate similar use cases.
   96 ///
   97 /// {@tool dartpad --template=stateful_widget_scaffold}
   98 ///
   99 /// ![Radio list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/radio_list_tile_semantics.png)
  100 ///
  101 /// Here is an example of a custom labeled radio widget, called
  102 /// LinkedLabelRadio, that includes an interactive [RichText] widget that
  103 /// handles tap gestures.
  104 ///
  105 /// ```dart imports
  106 /// import 'package:flutter/gestures.dart';
  107 /// ```
  108 /// ```dart preamble
  109 /// class LinkedLabelRadio extends StatelessWidget {
  110 ///   const LinkedLabelRadio({
  111 ///     this.label,
  112 ///     this.padding,
  113 ///     this.groupValue,
  114 ///     this.value,
  115 ///     this.onChanged,
  116 ///   });
  117 ///
  118 ///   final String label;
  119 ///   final EdgeInsets padding;
  120 ///   final bool groupValue;
  121 ///   final bool value;
  122 ///   final Function onChanged;
  123 ///
  124 ///   @override
  125 ///   Widget build(BuildContext context) {
  126 ///     return Padding(
  127 ///       padding: padding,
  128 ///       child: Row(
  129 ///         children: <Widget>[
  130 ///           Radio<bool>(
  131 ///             groupValue: groupValue,
  132 ///             value: value,
  133 ///             onChanged: (bool newValue) {
  134 ///               onChanged(newValue);
  135 ///             }
  136 ///           ),
  137 ///           RichText(
  138 ///             text: TextSpan(
  139 ///               text: label,
  140 ///               style: TextStyle(
  141 ///                 color: Colors.blueAccent,
  142 ///                 decoration: TextDecoration.underline,
  143 ///               ),
  144 ///               recognizer: TapGestureRecognizer()
  145 ///                 ..onTap = () {
  146 ///                 print('Label has been tapped.');
  147 ///               },
  148 ///             ),
  149 ///           ),
  150 ///         ],
  151 ///       ),
  152 ///     );
  153 ///   }
  154 /// }
  155 /// ```
  156 /// ```dart
  157 /// bool _isRadioSelected = false;
  158 ///
  159 /// @override
  160 /// Widget build(BuildContext context) {
  161 ///   return Scaffold(
  162 ///     body: Column(
  163 ///       mainAxisAlignment: MainAxisAlignment.center,
  164 ///       children: <Widget>[
  165 ///         LinkedLabelRadio(
  166 ///           label: 'First tappable label text',
  167 ///           padding: EdgeInsets.symmetric(horizontal: 5.0),
  168 ///           value: true,
  169 ///           groupValue: _isRadioSelected,
  170 ///           onChanged: (bool newValue) {
  171 ///             setState(() {
  172 ///               _isRadioSelected = newValue;
  173 ///             });
  174 ///           },
  175 ///         ),
  176 ///         LinkedLabelRadio(
  177 ///           label: 'Second tappable label text',
  178 ///           padding: EdgeInsets.symmetric(horizontal: 5.0),
  179 ///           value: false,
  180 ///           groupValue: _isRadioSelected,
  181 ///           onChanged: (bool newValue) {
  182 ///             setState(() {
  183 ///               _isRadioSelected = newValue;
  184 ///             });
  185 ///           },
  186 ///         ),
  187 ///       ],
  188 ///     ),
  189 ///   );
  190 /// }
  191 /// ```
  192 /// {@end-tool}
  193 ///
  194 /// ## RadioListTile isn't exactly what I want
  195 ///
  196 /// If the way RadioListTile pads and positions its elements isn't quite what
  197 /// you're looking for, you can create custom labeled radio widgets by
  198 /// combining [Radio] with other widgets, such as [Text], [Padding] and
  199 /// [InkWell].
  200 ///
  201 /// {@tool dartpad --template=stateful_widget_scaffold}
  202 ///
  203 /// ![Custom radio list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/radio_list_tile_custom.png)
  204 ///
  205 /// Here is an example of a custom LabeledRadio widget, but you can easily
  206 /// make your own configurable widget.
  207 ///
  208 /// ```dart preamble
  209 /// class LabeledRadio extends StatelessWidget {
  210 ///   const LabeledRadio({
  211 ///     this.label,
  212 ///     this.padding,
  213 ///     this.groupValue,
  214 ///     this.value,
  215 ///     this.onChanged,
  216 ///   });
  217 ///
  218 ///   final String label;
  219 ///   final EdgeInsets padding;
  220 ///   final bool groupValue;
  221 ///   final bool value;
  222 ///   final Function onChanged;
  223 ///
  224 ///   @override
  225 ///   Widget build(BuildContext context) {
  226 ///     return InkWell(
  227 ///       onTap: () {
  228 ///         if (value != groupValue)
  229 ///           onChanged(value);
  230 ///       },
  231 ///       child: Padding(
  232 ///         padding: padding,
  233 ///         child: Row(
  234 ///           children: <Widget>[
  235 ///             Radio<bool>(
  236 ///               groupValue: groupValue,
  237 ///               value: value,
  238 ///               onChanged: (bool newValue) {
  239 ///                 onChanged(newValue);
  240 ///               },
  241 ///             ),
  242 ///             Text(label),
  243 ///           ],
  244 ///         ),
  245 ///       ),
  246 ///     );
  247 ///   }
  248 /// }
  249 /// ```
  250 /// ```dart
  251 /// bool _isRadioSelected = false;
  252 ///
  253 /// @override
  254 /// Widget build(BuildContext context) {
  255 ///   return Scaffold(
  256 ///     body: Column(
  257 ///       mainAxisAlignment: MainAxisAlignment.center,
  258 ///       children: <LabeledRadio>[
  259 ///         LabeledRadio(
  260 ///           label: 'This is the first label text',
  261 ///           padding: const EdgeInsets.symmetric(horizontal: 5.0),
  262 ///           value: true,
  263 ///           groupValue: _isRadioSelected,
  264 ///           onChanged: (bool newValue) {
  265 ///             setState(() {
  266 ///               _isRadioSelected = newValue;
  267 ///             });
  268 ///           },
  269 ///         ),
  270 ///         LabeledRadio(
  271 ///           label: 'This is the second label text',
  272 ///           padding: const EdgeInsets.symmetric(horizontal: 5.0),
  273 ///           value: false,
  274 ///           groupValue: _isRadioSelected,
  275 ///           onChanged: (bool newValue) {
  276 ///             setState(() {
  277 ///               _isRadioSelected = newValue;
  278 ///             });
  279 ///           },
  280 ///         ),
  281 ///       ],
  282 ///     ),
  283 ///   );
  284 /// }
  285 /// ```
  286 /// {@end-tool}
  287 ///
  288 /// See also:
  289 ///
  290 ///  * [ListTileTheme], which can be used to affect the style of list tiles,
  291 ///    including radio list tiles.
  292 ///  * [CheckboxListTile], a similar widget for checkboxes.
  293 ///  * [SwitchListTile], a similar widget for switches.
  294 ///  * [ListTile] and [Radio], the widgets from which this widget is made.
  295 class RadioListTile<T> extends StatelessWidget {
  296   /// Creates a combination of a list tile and a radio button.
  297   ///
  298   /// The radio tile itself does not maintain any state. Instead, when the radio
  299   /// button is selected, the widget calls the [onChanged] callback. Most
  300   /// widgets that use a radio button will listen for the [onChanged] callback
  301   /// and rebuild the radio tile with a new [groupValue] to update the visual
  302   /// appearance of the radio button.
  303   ///
  304   /// The following arguments are required:
  305   ///
  306   /// * [value] and [groupValue] together determine whether the radio button is
  307   ///   selected.
  308   /// * [onChanged] is called when the user selects this radio button.
  309   const RadioListTile({
  310     Key key,
  311     @required this.value,
  312     @required this.groupValue,
  313     @required this.onChanged,
  314     this.toggleable = false,
  315     this.activeColor,
  316     this.title,
  317     this.subtitle,
  318     this.isThreeLine = false,
  319     this.dense,
  320     this.secondary,
  321     this.selected = false,
  322     this.controlAffinity = ListTileControlAffinity.platform,
  323     this.autofocus = false,
  324 
  325   }) : assert(toggleable != null),
  326        assert(isThreeLine != null),
  327        assert(!isThreeLine || subtitle != null),
  328        assert(selected != null),
  329        assert(controlAffinity != null),
  330        assert(autofocus != null),
  331        super(key: key);
  332 
  333   /// The value represented by this radio button.
  334   final T value;
  335 
  336   /// The currently selected value for this group of radio buttons.
  337   ///
  338   /// This radio button is considered selected if its [value] matches the
  339   /// [groupValue].
  340   final T groupValue;
  341 
  342   /// Called when the user selects this radio button.
  343   ///
  344   /// The radio button passes [value] as a parameter to this callback. The radio
  345   /// button does not actually change state until the parent widget rebuilds the
  346   /// radio tile with the new [groupValue].
  347   ///
  348   /// If null, the radio button will be displayed as disabled.
  349   ///
  350   /// The provided callback will not be invoked if this radio button is already
  351   /// selected.
  352   ///
  353   /// The callback provided to [onChanged] should update the state of the parent
  354   /// [StatefulWidget] using the [State.setState] method, so that the parent
  355   /// gets rebuilt; for example:
  356   ///
  357   /// ```dart
  358   /// RadioListTile<SingingCharacter>(
  359   ///   title: const Text('Lafayette'),
  360   ///   value: SingingCharacter.lafayette,
  361   ///   groupValue: _character,
  362   ///   onChanged: (SingingCharacter newValue) {
  363   ///     setState(() {
  364   ///       _character = newValue;
  365   ///     });
  366   ///   },
  367   /// )
  368   /// ```
  369   final ValueChanged<T> onChanged;
  370 
  371   /// Set to true if this radio list tile is allowed to be returned to an
  372   /// indeterminate state by selecting it again when selected.
  373   ///
  374   /// To indicate returning to an indeterminate state, [onChanged] will be
  375   /// called with null.
  376   ///
  377   /// If true, [onChanged] can be called with [value] when selected while
  378   /// [groupValue] != [value], or with null when selected again while
  379   /// [groupValue] == [value].
  380   ///
  381   /// If false, [onChanged] will be called with [value] when it is selected
  382   /// while [groupValue] != [value], and only by selecting another radio button
  383   /// in the group (i.e. changing the value of [groupValue]) can this radio
  384   /// list tile be unselected.
  385   ///
  386   /// The default is false.
  387   ///
  388   /// {@tool dartpad --template=stateful_widget_scaffold}
  389   /// This example shows how to enable deselecting a radio button by setting the
  390   /// [toggleable] attribute.
  391   ///
  392   /// ```dart
  393   /// int groupValue;
  394   /// static const List<String> selections = <String>[
  395   ///   'Hercules Mulligan',
  396   ///   'Eliza Hamilton',
  397   ///   'Philip Schuyler',
  398   ///   'Maria Reynolds',
  399   ///   'Samuel Seabury',
  400   /// ];
  401   ///
  402   /// @override
  403   /// Widget build(BuildContext context) {
  404   ///   return Scaffold(
  405   ///     body: ListView.builder(
  406   ///       itemBuilder: (context, index) {
  407   ///         return RadioListTile<int>(
  408   ///           value: index,
  409   ///           groupValue: groupValue,
  410   ///           toggleable: true,
  411   ///           title: Text(selections[index]),
  412   ///           onChanged: (int value) {
  413   ///             setState(() {
  414   ///               groupValue = value;
  415   ///             });
  416   ///           },
  417   ///         );
  418   ///       },
  419   ///       itemCount: selections.length,
  420   ///     ),
  421   ///   );
  422   /// }
  423   /// ```
  424   /// {@end-tool}
  425   final bool toggleable;
  426 
  427   /// The color to use when this radio button is selected.
  428   ///
  429   /// Defaults to accent color of the current [Theme].
  430   final Color activeColor;
  431 
  432   /// The primary content of the list tile.
  433   ///
  434   /// Typically a [Text] widget.
  435   final Widget title;
  436 
  437   /// Additional content displayed below the title.
  438   ///
  439   /// Typically a [Text] widget.
  440   final Widget subtitle;
  441 
  442   /// A widget to display on the opposite side of the tile from the radio button.
  443   ///
  444   /// Typically an [Icon] widget.
  445   final Widget secondary;
  446 
  447   /// Whether this list tile is intended to display three lines of text.
  448   ///
  449   /// If false, the list tile is treated as having one line if the subtitle is
  450   /// null and treated as having two lines if the subtitle is non-null.
  451   final bool isThreeLine;
  452 
  453   /// Whether this list tile is part of a vertically dense list.
  454   ///
  455   /// If this property is null then its value is based on [ListTileTheme.dense].
  456   final bool dense;
  457 
  458   /// Whether to render icons and text in the [activeColor].
  459   ///
  460   /// No effort is made to automatically coordinate the [selected] state and the
  461   /// [checked] state. To have the list tile appear selected when the radio
  462   /// button is the selected radio button, set [selected] to true when [value]
  463   /// matches [groupValue].
  464   ///
  465   /// Normally, this property is left to its default value, false.
  466   final bool selected;
  467 
  468   /// Where to place the control relative to the text.
  469   final ListTileControlAffinity controlAffinity;
  470 
  471   /// {@macro flutter.widgets.Focus.autofocus}
  472   final bool autofocus;
  473 
  474   /// Whether this radio button is checked.
  475   ///
  476   /// To control this value, set [value] and [groupValue] appropriately.
  477   bool get checked => value == groupValue;
  478 
  479   @override
  480   Widget build(BuildContext context) {
  481     final Widget control = Radio<T>(
  482       value: value,
  483       groupValue: groupValue,
  484       onChanged: onChanged,
  485       toggleable: toggleable,
  486       activeColor: activeColor,
  487       materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  488       autofocus: autofocus,
  489     );
  490     Widget leading, trailing;
  491     switch (controlAffinity) {
  492       case ListTileControlAffinity.leading:
  493       case ListTileControlAffinity.platform:
  494         leading = control;
  495         trailing = secondary;
  496         break;
  497       case ListTileControlAffinity.trailing:
  498         leading = secondary;
  499         trailing = control;
  500         break;
  501     }
  502     return MergeSemantics(
  503       child: ListTileTheme.merge(
  504         selectedColor: activeColor ?? Theme.of(context).accentColor,
  505         child: ListTile(
  506           leading: leading,
  507           title: title,
  508           subtitle: subtitle,
  509           trailing: trailing,
  510           isThreeLine: isThreeLine,
  511           dense: dense,
  512           enabled: onChanged != null,
  513           onTap: onChanged != null ? () {
  514             if (toggleable && checked) {
  515               onChanged(null);
  516               return;
  517             }
  518             if (!checked) {
  519               onChanged(value);
  520             }
  521           } : null,
  522           selected: selected,
  523           autofocus: autofocus,
  524         ),
  525       ),
  526     );
  527   }
  528 }