CheckboxListTile, RadioListTile, SwitchListTile (#10160)
This commit is contained in:
parent
d3fbf2ae6f
commit
cd6e3b1ac2
@ -132,77 +132,52 @@ class GalleryDrawer extends StatelessWidget {
|
||||
final TextStyle aboutTextStyle = themeData.textTheme.body2;
|
||||
final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor);
|
||||
|
||||
final Widget lightThemeItem = new ListTile(
|
||||
leading: const Icon(Icons.brightness_5),
|
||||
final Widget lightThemeItem = new RadioListTile<bool>(
|
||||
secondary: const Icon(Icons.brightness_5),
|
||||
title: const Text('Light'),
|
||||
trailing: new Radio<bool>(
|
||||
value: true,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged,
|
||||
),
|
||||
value: true,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged,
|
||||
selected: useLightTheme,
|
||||
onTap: () {
|
||||
onThemeChanged(true);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget darkThemeItem = new ListTile(
|
||||
leading: const Icon(Icons.brightness_7),
|
||||
final Widget darkThemeItem = new RadioListTile<bool>(
|
||||
secondary: const Icon(Icons.brightness_7),
|
||||
title: const Text('Dark'),
|
||||
trailing: new Radio<bool>(
|
||||
value: false,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged
|
||||
),
|
||||
value: false,
|
||||
groupValue: useLightTheme,
|
||||
onChanged: onThemeChanged,
|
||||
selected: !useLightTheme,
|
||||
onTap: () {
|
||||
onThemeChanged(false);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget mountainViewItem = new ListTile(
|
||||
final Widget mountainViewItem = new RadioListTile<TargetPlatform>(
|
||||
// on iOS, we don't want to show an Android phone icon
|
||||
leading: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star : Icons.phone_android),
|
||||
secondary: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star : Icons.phone_android),
|
||||
title: const Text('Android'),
|
||||
trailing: new Radio<TargetPlatform>(
|
||||
value: TargetPlatform.android,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
),
|
||||
value: TargetPlatform.android,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
selected: Theme.of(context).platform == TargetPlatform.android,
|
||||
onTap: () {
|
||||
onPlatformChanged(TargetPlatform.android);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget cupertinoItem = new ListTile(
|
||||
final Widget cupertinoItem = new RadioListTile<TargetPlatform>(
|
||||
// on iOS, we don't want to show the iPhone icon
|
||||
leading: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star_border : Icons.phone_iphone),
|
||||
secondary: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star_border : Icons.phone_iphone),
|
||||
title: const Text('iOS'),
|
||||
trailing: new Radio<TargetPlatform>(
|
||||
value: TargetPlatform.iOS,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
),
|
||||
value: TargetPlatform.iOS,
|
||||
groupValue: Theme.of(context).platform,
|
||||
onChanged: onPlatformChanged,
|
||||
selected: Theme.of(context).platform == TargetPlatform.iOS,
|
||||
onTap: () {
|
||||
onPlatformChanged(TargetPlatform.iOS);
|
||||
},
|
||||
);
|
||||
|
||||
final Widget animateSlowlyItem = new ListTile(
|
||||
leading: const Icon(Icons.hourglass_empty),
|
||||
final Widget animateSlowlyItem = new CheckboxListTile(
|
||||
title: const Text('Animate Slowly'),
|
||||
trailing: new Checkbox(
|
||||
value: timeDilation != 1.0,
|
||||
onChanged: (bool value) {
|
||||
onTimeDilationChanged(value ? 20.0 : 1.0);
|
||||
},
|
||||
),
|
||||
selected: timeDilation != 1.0,
|
||||
onTap: () {
|
||||
onTimeDilationChanged(timeDilation != 1.0 ? 1.0 : 20.0);
|
||||
value: timeDilation != 1.0,
|
||||
onChanged: (bool value) {
|
||||
onTimeDilationChanged(value ? 20.0 : 1.0);
|
||||
},
|
||||
secondary: const Icon(Icons.hourglass_empty),
|
||||
selected: timeDilation != 1.0,
|
||||
);
|
||||
|
||||
final Widget sendFeedbackItem = new ListTile(
|
||||
@ -271,53 +246,32 @@ class GalleryDrawer extends StatelessWidget {
|
||||
];
|
||||
|
||||
if (onShowPerformanceOverlayChanged != null) {
|
||||
allDrawerItems.insert(8, new ListTile(
|
||||
leading: const Icon(Icons.assessment),
|
||||
allDrawerItems.insert(8, new CheckboxListTile(
|
||||
title: const Text('Performance Overlay'),
|
||||
trailing: new Checkbox(
|
||||
value: showPerformanceOverlay,
|
||||
onChanged: (bool value) {
|
||||
onShowPerformanceOverlayChanged(!showPerformanceOverlay);
|
||||
},
|
||||
),
|
||||
value: showPerformanceOverlay,
|
||||
onChanged: onShowPerformanceOverlayChanged,
|
||||
secondary: const Icon(Icons.assessment),
|
||||
selected: showPerformanceOverlay,
|
||||
onTap: () {
|
||||
onShowPerformanceOverlayChanged(!showPerformanceOverlay);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if (onCheckerboardRasterCacheImagesChanged != null) {
|
||||
allDrawerItems.insert(8, new ListTile(
|
||||
leading: const Icon(Icons.assessment),
|
||||
allDrawerItems.insert(8, new CheckboxListTile(
|
||||
title: const Text('Checkerboard Raster Cache Images'),
|
||||
trailing: new Checkbox(
|
||||
value: checkerboardRasterCacheImages,
|
||||
onChanged: (bool value) {
|
||||
onCheckerboardRasterCacheImagesChanged(!checkerboardRasterCacheImages);
|
||||
},
|
||||
),
|
||||
value: checkerboardRasterCacheImages,
|
||||
onChanged: onCheckerboardRasterCacheImagesChanged,
|
||||
secondary: const Icon(Icons.assessment),
|
||||
selected: checkerboardRasterCacheImages,
|
||||
onTap: () {
|
||||
onCheckerboardRasterCacheImagesChanged(!checkerboardRasterCacheImages);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if (onCheckerboardOffscreenLayersChanged != null) {
|
||||
allDrawerItems.insert(8, new ListTile(
|
||||
leading: const Icon(Icons.assessment),
|
||||
allDrawerItems.insert(8, new CheckboxListTile(
|
||||
title: const Text('Checkerboard Offscreen Layers'),
|
||||
trailing: new Checkbox(
|
||||
value: checkerboardOffscreenLayers,
|
||||
onChanged: (bool value) {
|
||||
onCheckerboardOffscreenLayersChanged(!checkerboardOffscreenLayers);
|
||||
},
|
||||
),
|
||||
value: checkerboardOffscreenLayers,
|
||||
onChanged: onCheckerboardOffscreenLayersChanged,
|
||||
secondary: const Icon(Icons.assessment),
|
||||
selected: checkerboardOffscreenLayers,
|
||||
onTap: () {
|
||||
onCheckerboardOffscreenLayersChanged(!checkerboardOffscreenLayers);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ export 'src/material/button.dart';
|
||||
export 'src/material/button_bar.dart';
|
||||
export 'src/material/card.dart';
|
||||
export 'src/material/checkbox.dart';
|
||||
export 'src/material/checkbox_list_tile.dart';
|
||||
export 'src/material/chip.dart';
|
||||
export 'src/material/circle_avatar.dart';
|
||||
export 'src/material/colors.dart';
|
||||
@ -65,6 +66,7 @@ export 'src/material/paginated_data_table.dart';
|
||||
export 'src/material/popup_menu.dart';
|
||||
export 'src/material/progress_indicator.dart';
|
||||
export 'src/material/radio.dart';
|
||||
export 'src/material/radio_list_tile.dart';
|
||||
export 'src/material/raised_button.dart';
|
||||
export 'src/material/refresh_indicator.dart';
|
||||
export 'src/material/scaffold.dart';
|
||||
@ -74,6 +76,7 @@ export 'src/material/slider.dart';
|
||||
export 'src/material/snack_bar.dart';
|
||||
export 'src/material/stepper.dart';
|
||||
export 'src/material/switch.dart';
|
||||
export 'src/material/switch_list_tile.dart';
|
||||
export 'src/material/tab_controller.dart';
|
||||
export 'src/material/tabs.dart';
|
||||
export 'src/material/text_field.dart';
|
||||
|
@ -13,7 +13,7 @@ import 'debug.dart';
|
||||
import 'theme.dart';
|
||||
import 'toggleable.dart';
|
||||
|
||||
/// A material design checkbox
|
||||
/// A material design checkbox.
|
||||
///
|
||||
/// The checkbox itself does not maintain any state. Instead, when the state of
|
||||
/// the checkbox changes, the widget calls the [onChanged] callback. Most
|
||||
@ -25,7 +25,9 @@ import 'toggleable.dart';
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Switch], another widget with similar semantics.
|
||||
/// * [CheckboxListTile], which combines this widget with a [ListTile] so that
|
||||
/// you can give the checkbox a label.
|
||||
/// * [Switch], a widget with semantics similar to [Checkbox].
|
||||
/// * [Radio], for selecting among a set of explicit values.
|
||||
/// * [Slider], for selecting a value in a range.
|
||||
/// * <https://material.google.com/components/selection-controls.html#selection-controls-checkbox>
|
||||
@ -39,16 +41,23 @@ class Checkbox extends StatefulWidget {
|
||||
/// rebuild the checkbox with a new [value] to update the visual appearance of
|
||||
/// the checkbox.
|
||||
///
|
||||
/// * [value] determines whether the checkbox is checked.
|
||||
/// * [onChanged] is called when the value of the checkbox should change.
|
||||
/// The following arguments are required:
|
||||
///
|
||||
/// * [value], which determines whether the checkbox is checked, and must not
|
||||
/// be null.
|
||||
/// * [onChanged], which is called when the value of the checkbox should
|
||||
/// change. It can be set to null to disable the checkbox.
|
||||
const Checkbox({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@required this.onChanged,
|
||||
this.activeColor
|
||||
}) : super(key: key);
|
||||
this.activeColor,
|
||||
}) : assert(value != null),
|
||||
super(key: key);
|
||||
|
||||
/// Whether this checkbox is checked.
|
||||
///
|
||||
/// This property must not be null.
|
||||
final bool value;
|
||||
|
||||
/// Called when the value of the checkbox should change.
|
||||
@ -59,7 +68,7 @@ class Checkbox extends StatefulWidget {
|
||||
///
|
||||
/// If null, the checkbox will be displayed as disabled.
|
||||
///
|
||||
/// The callback provided to onChanged should update the state of the parent
|
||||
/// 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:
|
||||
///
|
||||
|
204
packages/flutter/lib/src/material/checkbox_list_tile.dart
Normal file
204
packages/flutter/lib/src/material/checkbox_list_tile.dart
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'checkbox.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// The [value], [onChanged], and [activeColor] properties of this widget are
|
||||
/// identical to the similarly-named properties on the [Checkbox] 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]
|
||||
/// property, but the color used is that described by [activeColor], if any,
|
||||
/// defaulting to the accent color of the current [Theme]. No effort is made to
|
||||
/// 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.
|
||||
///
|
||||
/// 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].
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
/// This widget shows a checkbox that, when checked, slows down all animations
|
||||
/// (including the animation of the checkbox itself getting checked!).
|
||||
///
|
||||
/// ```dart
|
||||
/// new CheckboxListTile(
|
||||
/// title: const Text('Animate Slowly'),
|
||||
/// value: timeDilation != 1.0,
|
||||
/// onChanged: (bool value) {
|
||||
/// setState(() { timeDilation = value ? 20.0 : 1.0; });
|
||||
/// },
|
||||
/// secondary: const Icon(Icons.hourglass_empty),
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// This sample requires that you also import 'package:flutter/scheduler.dart',
|
||||
/// so that you can reference [timeDilation].
|
||||
///
|
||||
/// 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:
|
||||
///
|
||||
/// * [value], which determines whether the checkbox is checked, and must not
|
||||
/// be null.
|
||||
///
|
||||
/// * [onChanged], which is called when the value of the checkbox should
|
||||
/// change. It can be set to null to disable the checkbox.
|
||||
const CheckboxListTile({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@required this.onChanged,
|
||||
this.activeColor,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.isThreeLine: false,
|
||||
this.dense,
|
||||
this.secondary,
|
||||
this.selected: false,
|
||||
this.controlAffinity: ListTileControlAffinity.platform,
|
||||
}) : assert(value != null),
|
||||
assert(isThreeLine != null),
|
||||
assert(!isThreeLine || subtitle != null),
|
||||
assert(selected != null),
|
||||
assert(controlAffinity != null),
|
||||
super(key: key);
|
||||
|
||||
/// Whether this checkbox is checked.
|
||||
///
|
||||
/// This property must not be null.
|
||||
final bool value;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
/// new CheckboxListTile(
|
||||
/// value: _throwShotAway,
|
||||
/// onChanged: (bool newValue) {
|
||||
/// setState(() {
|
||||
/// _throwShotAway = newValue;
|
||||
/// });
|
||||
/// },
|
||||
/// title: new Text('Throw away your shot'),
|
||||
/// ),
|
||||
/// ```
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
/// The color to use when this checkbox is checked.
|
||||
///
|
||||
/// Defaults to accent color of the current [Theme].
|
||||
final Color activeColor;
|
||||
|
||||
/// The primary content of the list tile.
|
||||
///
|
||||
/// Typically a [Text] widget.
|
||||
final Widget title;
|
||||
|
||||
/// Additional content displayed below the title.
|
||||
///
|
||||
/// Typically a [Text] widget.
|
||||
final Widget subtitle;
|
||||
|
||||
/// A widget to display on the opposite side of the tile from the checkbox.
|
||||
///
|
||||
/// Typically an [Icon] widget.
|
||||
final Widget secondary;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// If this property is null then its value is based on [ListTileTheme.dense].
|
||||
final bool dense;
|
||||
|
||||
/// 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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget control = new Checkbox(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: activeColor,
|
||||
);
|
||||
Widget leading, trailing;
|
||||
switch (controlAffinity) {
|
||||
case ListTileControlAffinity.leading:
|
||||
leading = control;
|
||||
trailing = secondary;
|
||||
break;
|
||||
case ListTileControlAffinity.trailing:
|
||||
case ListTileControlAffinity.platform:
|
||||
leading = secondary;
|
||||
trailing = control;
|
||||
break;
|
||||
}
|
||||
return new MergeSemantics(
|
||||
child: ListTileTheme.merge(
|
||||
selectedColor: activeColor ?? Theme.of(context).accentColor,
|
||||
child: new ListTile(
|
||||
leading: leading,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
trailing: trailing,
|
||||
isThreeLine: isThreeLine,
|
||||
dense: dense,
|
||||
enabled: onChanged != null,
|
||||
onTap: onChanged != null ? () { onChanged(!value); } : null,
|
||||
selected: selected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class IconTheme extends InheritedWidget {
|
||||
const IconTheme({
|
||||
Key key,
|
||||
@required this.data,
|
||||
@required Widget child
|
||||
@required Widget child,
|
||||
}) : assert(data != null),
|
||||
assert(child != null),
|
||||
super(key: key, child: child);
|
||||
@ -31,7 +31,7 @@ class IconTheme extends InheritedWidget {
|
||||
static Widget merge({
|
||||
Key key,
|
||||
@required IconThemeData data,
|
||||
@required Widget child
|
||||
@required Widget child,
|
||||
}) {
|
||||
return new Builder(
|
||||
builder: (BuildContext context) {
|
||||
|
@ -35,7 +35,8 @@ enum ListTileStyle {
|
||||
/// The [Drawer] widget specifies a tile theme for its children which sets
|
||||
/// [style] to [ListTileStyle.drawer].
|
||||
class ListTileTheme extends InheritedWidget {
|
||||
/// Creates an inherited widget that defines color and style parameters for [ListTile]s.
|
||||
/// Creates a list tile theme that controls the color and style parameters for
|
||||
/// [ListTile]s.
|
||||
const ListTileTheme({
|
||||
Key key,
|
||||
this.dense: false,
|
||||
@ -46,6 +47,36 @@ class ListTileTheme extends InheritedWidget {
|
||||
Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// Creates a list tile theme that controls the color and style parameters for
|
||||
/// [ListTile]s, and merges in the current list tile theme, if any.
|
||||
///
|
||||
/// The [child] argument must not be null.
|
||||
static Widget merge({
|
||||
Key key,
|
||||
bool dense,
|
||||
ListTileStyle style,
|
||||
Color selectedColor,
|
||||
Color iconColor,
|
||||
Color textColor,
|
||||
@required Widget child,
|
||||
}) {
|
||||
assert(child != null);
|
||||
return new Builder(
|
||||
builder: (BuildContext context) {
|
||||
final ListTileTheme parent = ListTileTheme.of(context);
|
||||
return new ListTileTheme(
|
||||
key: key,
|
||||
dense: dense ?? parent.dense,
|
||||
style: style ?? parent.style,
|
||||
selectedColor: selectedColor ?? parent.selectedColor,
|
||||
iconColor: iconColor ?? parent.iconColor,
|
||||
textColor: textColor ?? parent.textColor,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// If true then [ListTile]s will have the vertically dense layout.
|
||||
final bool dense;
|
||||
|
||||
@ -83,6 +114,28 @@ class ListTileTheme extends InheritedWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where to place the control in widgets that use [ListTile] to position a
|
||||
/// control next to a label.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CheckboxListTile], which combines a [ListTile] with a [Checkbox].
|
||||
/// * [RadioListTile], which combines a [ListTile] with a [Radio] button.
|
||||
enum ListTileControlAffinity {
|
||||
/// Position the control on the leading edge, and the secondary widget, if
|
||||
/// any, on the trailing edge.
|
||||
leading,
|
||||
|
||||
/// Position the control on the trailing edge, and the secondary widget, if
|
||||
/// any, on the leading edge.
|
||||
trailing,
|
||||
|
||||
/// Position the control relative to the text in the fashion that is typical
|
||||
/// for the current platform, and place the secondary widget on the opposite
|
||||
/// side.
|
||||
platform,
|
||||
}
|
||||
|
||||
/// A single fixed-height row that typically contains some text as well as
|
||||
/// a leading or trailing icon.
|
||||
///
|
||||
@ -114,7 +167,8 @@ class ListTileTheme extends InheritedWidget {
|
||||
/// * [Card], which can be used with [Column] to show a few [ListTile]s.
|
||||
/// * [Divider], which can be used to separate [ListTile]s.
|
||||
/// * [ListTile.divideTiles], a utility for inserting [Divider]s in between [ListTile]s.
|
||||
/// * [kListTileExtent], which defines the ListTile sizes.
|
||||
/// * [CheckboxListTile], [RadioListTile], and [SwitchListTile], widgets
|
||||
/// that combine [ListTile] with other controls.
|
||||
/// * <https://material.google.com/components/lists.html>
|
||||
class ListTile extends StatelessWidget {
|
||||
/// Creates a list tile.
|
||||
@ -314,7 +368,7 @@ class ListTile extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(right: 16.0),
|
||||
width: 40.0,
|
||||
alignment: FractionalOffset.centerLeft,
|
||||
child: leading
|
||||
child: leading,
|
||||
),
|
||||
));
|
||||
}
|
||||
@ -335,8 +389,8 @@ class ListTile extends StatelessWidget {
|
||||
style: _subtitleTextStyle(theme, tileTheme),
|
||||
duration: kThemeChangeDuration,
|
||||
child: subtitle,
|
||||
)
|
||||
]
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
children.add(new Expanded(
|
||||
|
@ -17,9 +17,10 @@ const double _kInnerRadius = 5.0;
|
||||
|
||||
/// A material design radio button.
|
||||
///
|
||||
/// Used to select between a number of mutually exclusive values. When one
|
||||
/// radio button in a group is selected, the other radio buttons in the group
|
||||
/// cease to be selected.
|
||||
/// Used to select between a number of mutually exclusive values. When one radio
|
||||
/// button in a group is selected, the other radio buttons in the group cease to
|
||||
/// be selected. The values are of type `T`, the type parameter of the [Radio]
|
||||
/// class. Enums are commonly used for this purpose.
|
||||
///
|
||||
/// The radio button itself does not maintain any state. Instead, when the state
|
||||
/// of the radio button changes, the widget calls the [onChanged] callback.
|
||||
@ -31,20 +32,25 @@ const double _kInnerRadius = 5.0;
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RadioListTile], which combines this widget with a [ListTile] so that
|
||||
/// you can give the radio button a label.
|
||||
/// * [Slider], for selecting a value in a range.
|
||||
/// * [Checkbox] and [Switch], for toggling a particular value on or off.
|
||||
/// * <https://material.google.com/components/selection-controls.html#selection-controls-radio-button>
|
||||
class Radio<T> extends StatefulWidget {
|
||||
/// Creates a material design radio button.
|
||||
///
|
||||
/// The radio button itself does not maintain any state. Instead, when the state
|
||||
/// of the radio button changes, the widget calls the [onChanged] callback.
|
||||
/// Most widget that use a radio button will listen for the [onChanged]
|
||||
/// callback and rebuild the radio button with a new [groupValue] to update the
|
||||
/// visual appearance of the radio button.
|
||||
/// The radio button itself does not maintain any state. Instead, when the
|
||||
/// radio button is selected, the widget calls the [onChanged] callback. Most
|
||||
/// widgets that use a radio button will listen for the [onChanged] callback
|
||||
/// and rebuild the radio button with a new [groupValue] to update the visual
|
||||
/// appearance of the radio button.
|
||||
///
|
||||
/// * [value] and [groupValue] together determines whether the radio button is selected.
|
||||
/// * [onChanged] is when the user selects this radio button.
|
||||
/// The following arguments are required:
|
||||
///
|
||||
/// * [value] and [groupValue] together determine whether the radio button is
|
||||
/// selected.
|
||||
/// * [onChanged] is called when the user selects this radio button.
|
||||
const Radio({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@ -70,7 +76,7 @@ class Radio<T> extends StatefulWidget {
|
||||
///
|
||||
/// If null, the radio button will be displayed as disabled.
|
||||
///
|
||||
/// The callback provided to onChanged should update the state of the parent
|
||||
/// 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:
|
||||
///
|
||||
|
229
packages/flutter/lib/src/material/radio_list_tile.dart
Normal file
229
packages/flutter/lib/src/material/radio_list_tile.dart
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'list_tile.dart';
|
||||
import 'radio.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// A [ListTile] with a [Radio]. In other words, a radio button with a label.
|
||||
///
|
||||
/// The entire list tile is interactive: tapping anywhere in the tile selects
|
||||
/// the radio button.
|
||||
///
|
||||
/// The [value], [groupValue], [onChanged], and [activeColor] properties of this
|
||||
/// widget are identical to the similarly-named properties on the [Radio]
|
||||
/// widget. The type parameter `T` serves the same purpose as that of the
|
||||
/// [Radio] class' type parameter.
|
||||
///
|
||||
/// 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]
|
||||
/// property, but the color used is that described by [activeColor], if any,
|
||||
/// defaulting to the accent color of the current [Theme]. No effort is made to
|
||||
/// coordinate the [selected] state and the [checked] state; to have the list
|
||||
/// tile appear selected when the radio button is the selected radio button, set
|
||||
/// [selected] to true when [value] matches [groupValue].
|
||||
///
|
||||
/// The radio button is shown on the left by default in left-to-right languages
|
||||
/// (i.e. the leading 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].
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
/// This widget shows a pair of radio buttons that control the `_character`
|
||||
/// field. The field is of the type `SingingCharacter`, an enum.
|
||||
///
|
||||
/// ```dart
|
||||
/// // At the top level:
|
||||
/// enum SingingCharacter { lafayette, jefferson }
|
||||
///
|
||||
/// // In the State of a stateful widget:
|
||||
/// SingingCharacter _character = SingingCharacter.lafayette;
|
||||
///
|
||||
/// // In the build function of that State:
|
||||
/// new Column(
|
||||
/// children: <Widget>[
|
||||
/// new RadioListTile<SingingCharacter>(
|
||||
/// title: const Text('Lafayette'),
|
||||
/// value: SingingCharacter.lafayette,
|
||||
/// groupValue: _character,
|
||||
/// onChanged: (SingingCharacter value) { setState(() { _character = value; }); },
|
||||
/// ),
|
||||
/// new RadioListTile<SingingCharacter>(
|
||||
/// title: const Text('Thomas Jefferson'),
|
||||
/// value: SingingCharacter.jefferson,
|
||||
/// groupValue: _character,
|
||||
/// onChanged: (SingingCharacter value) { setState(() { _character = value; }); },
|
||||
/// ),
|
||||
/// ],
|
||||
/// ),
|
||||
/// ```
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ListTileTheme], which can be used to affect the style of list tiles,
|
||||
/// including radio list tiles.
|
||||
/// * [CheckboxListTile], a similar widget for checkboxes.
|
||||
/// * [SwitchListTile], a similar widget for switches.
|
||||
/// * [ListTile] and [Radio], the widgets from which this widget is made.
|
||||
class RadioListTile<T> extends StatelessWidget {
|
||||
/// Creates a combination of a list tile and a radio button.
|
||||
///
|
||||
/// The radio tile itself does not maintain any state. Instead, when the radio
|
||||
/// button is selected, the widget calls the [onChanged] callback. Most
|
||||
/// widgets that use a radio button will listen for the [onChanged] callback
|
||||
/// and rebuild the radio tile with a new [groupValue] to update the visual
|
||||
/// appearance of the radio button.
|
||||
///
|
||||
/// The following arguments are required:
|
||||
///
|
||||
/// * [value] and [groupValue] together determine whether the radio button is
|
||||
/// selected.
|
||||
/// * [onChanged] is called when the user selects this radio button.
|
||||
const RadioListTile({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@required this.groupValue,
|
||||
@required this.onChanged,
|
||||
this.activeColor,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.isThreeLine: false,
|
||||
this.dense,
|
||||
this.secondary,
|
||||
this.selected: false,
|
||||
this.controlAffinity: ListTileControlAffinity.platform,
|
||||
}) : assert(isThreeLine != null),
|
||||
assert(!isThreeLine || subtitle != null),
|
||||
assert(selected != null),
|
||||
assert(controlAffinity != null),
|
||||
super(key: key);
|
||||
|
||||
/// The value represented by this radio button.
|
||||
final T value;
|
||||
|
||||
/// The currently selected value for this group of radio buttons.
|
||||
///
|
||||
/// This radio button is considered selected if its [value] matches the
|
||||
/// [groupValue].
|
||||
final T groupValue;
|
||||
|
||||
/// Called when the user selects this radio button.
|
||||
///
|
||||
/// The radio button passes [value] as a parameter to this callback. The radio
|
||||
/// button does not actually change state until the parent widget rebuilds the
|
||||
/// radio tile with the new [groupValue].
|
||||
///
|
||||
/// If null, the radio button 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:
|
||||
///
|
||||
/// ```dart
|
||||
/// new RadioListTile<SingingCharacter>(
|
||||
/// title: const Text('Lafayette'),
|
||||
/// value: SingingCharacter.lafayette,
|
||||
/// groupValue: _character,
|
||||
/// onChanged: (SingingCharacter newValue) {
|
||||
/// setState(() {
|
||||
/// _character = newValue;
|
||||
/// });
|
||||
/// },
|
||||
/// ),
|
||||
/// ```
|
||||
final ValueChanged<T> onChanged;
|
||||
|
||||
/// The color to use when this radio button is selected.
|
||||
///
|
||||
/// Defaults to accent color of the current [Theme].
|
||||
final Color activeColor;
|
||||
|
||||
/// The primary content of the list tile.
|
||||
///
|
||||
/// Typically a [Text] widget.
|
||||
final Widget title;
|
||||
|
||||
/// Additional content displayed below the title.
|
||||
///
|
||||
/// Typically a [Text] widget.
|
||||
final Widget subtitle;
|
||||
|
||||
/// A widget to display on the opposite side of the tile from the radio button.
|
||||
///
|
||||
/// Typically an [Icon] widget.
|
||||
final Widget secondary;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// If this property is null then its value is based on [ListTileTheme.dense].
|
||||
final bool dense;
|
||||
|
||||
/// Whether to render icons and text in the [activeColor].
|
||||
///
|
||||
/// No effort is made to automatically coordinate the [selected] state and the
|
||||
/// [checked] state. To have the list tile appear selected when the radio
|
||||
/// button is the selected radio button, set [selected] to true when [value]
|
||||
/// matches [groupValue].
|
||||
///
|
||||
/// 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;
|
||||
|
||||
/// Whether this radio button is checked.
|
||||
///
|
||||
/// To control this value, set [value] and [groupValue] appropriately.
|
||||
bool get checked => value == groupValue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget control = new Radio<T>(
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged,
|
||||
activeColor: activeColor,
|
||||
);
|
||||
Widget leading, trailing;
|
||||
switch (controlAffinity) {
|
||||
case ListTileControlAffinity.leading:
|
||||
case ListTileControlAffinity.platform:
|
||||
leading = control;
|
||||
trailing = secondary;
|
||||
break;
|
||||
case ListTileControlAffinity.trailing:
|
||||
leading = secondary;
|
||||
trailing = control;
|
||||
break;
|
||||
}
|
||||
return new MergeSemantics(
|
||||
child: ListTileTheme.merge(
|
||||
selectedColor: activeColor ?? Theme.of(context).accentColor,
|
||||
child: new ListTile(
|
||||
leading: leading,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
trailing: trailing,
|
||||
isThreeLine: isThreeLine,
|
||||
dense: dense,
|
||||
enabled: onChanged != null,
|
||||
onTap: onChanged != null ? () { onChanged(value); } : null,
|
||||
selected: selected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -28,6 +28,8 @@ import 'toggleable.dart';
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SwitchListTile], which combines this widget with a [ListTile] so that
|
||||
/// you can give the switch a label.
|
||||
/// * [Checkbox], another widget with similar semantics.
|
||||
/// * [Radio], for selecting among a set of explicit values.
|
||||
/// * [Slider], for selecting a value in a range.
|
||||
@ -40,8 +42,10 @@ class Switch extends StatefulWidget {
|
||||
/// that use a switch will listen for the [onChanged] callback and rebuild the
|
||||
/// switch with a new [value] to update the visual appearance of the switch.
|
||||
///
|
||||
/// * [value] determines this switch is on or off.
|
||||
/// * [onChanged] is called when the user toggles with switch on or off.
|
||||
/// 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 Switch({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@ -52,9 +56,11 @@ class Switch extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
/// Whether this switch is on or off.
|
||||
///
|
||||
/// This property must not be null.
|
||||
final bool value;
|
||||
|
||||
/// Called when the user toggles with switch on or off.
|
||||
/// 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 with the new
|
||||
@ -62,7 +68,7 @@ class Switch extends StatefulWidget {
|
||||
///
|
||||
/// If null, the switch will be displayed as disabled.
|
||||
///
|
||||
/// The callback provided to onChanged should update the state of the parent
|
||||
/// 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:
|
||||
///
|
||||
|
190
packages/flutter/lib/src/material/switch_list_tile.dart
Normal file
190
packages/flutter/lib/src/material/switch_list_tile.dart
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'list_tile.dart';
|
||||
import 'switch.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// A [ListTile] with a [Switch]. In other words, a switch with a label.
|
||||
///
|
||||
/// The entire list tile is interactive: tapping anywhere in the tile toggles
|
||||
/// the switch.
|
||||
///
|
||||
/// 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]
|
||||
/// property, but the color used is that described by [activeColor], if any,
|
||||
/// defaulting to the accent color of the current [Theme]. No effort is made to
|
||||
/// 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.
|
||||
///
|
||||
/// The switch is shown on the right by default in left-to-right languages (i.e.
|
||||
/// in the [ListTile.trailing] slot). The [secondary] widget is placed in the
|
||||
/// [ListTile.leading] slot. This cannot be changed; there is not sufficient
|
||||
/// space in a [ListTile]'s [ListTile.leading] slot for a [Switch].
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
/// This widget shows a switch that, when toggled, changes the state of a [bool]
|
||||
/// member field called `_lights`.
|
||||
///
|
||||
/// ```dart
|
||||
/// new SwitchListTile(
|
||||
/// title: const Text('Lights'),
|
||||
/// value: _lights,
|
||||
/// onChanged: (bool value) { setState(() { _lights = value; }); },
|
||||
/// secondary: const Icon(Icons.lightbulb_outline),
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// 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({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@required this.onChanged,
|
||||
this.activeColor,
|
||||
this.activeThumbImage,
|
||||
this.inactiveThumbImage,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.isThreeLine: false,
|
||||
this.dense,
|
||||
this.secondary,
|
||||
this.selected: false,
|
||||
}) : assert(value != null),
|
||||
assert(isThreeLine != null),
|
||||
assert(!isThreeLine || subtitle != null),
|
||||
assert(selected != null),
|
||||
super(key: key);
|
||||
|
||||
/// 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:
|
||||
///
|
||||
/// ```dart
|
||||
/// new SwitchListTile(
|
||||
/// value: _lights,
|
||||
/// onChanged: (bool newValue) {
|
||||
/// setState(() {
|
||||
/// _lights = newValue;
|
||||
/// });
|
||||
/// },
|
||||
/// title: new Text('Lights'),
|
||||
/// ),
|
||||
/// ```
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
/// The color to use when this switch is on.
|
||||
///
|
||||
/// Defaults to accent color of the current [Theme].
|
||||
final Color activeColor;
|
||||
|
||||
/// An image to use on the thumb of this switch when the switch is on.
|
||||
final ImageProvider activeThumbImage;
|
||||
|
||||
/// An image to use on the thumb of this switch when the switch is off.
|
||||
final ImageProvider inactiveThumbImage;
|
||||
|
||||
/// The primary content of the list tile.
|
||||
///
|
||||
/// Typically a [Text] widget.
|
||||
final Widget title;
|
||||
|
||||
/// Additional content displayed below the title.
|
||||
///
|
||||
/// Typically a [Text] widget.
|
||||
final Widget subtitle;
|
||||
|
||||
/// A widget to display on the opposite side of the tile from the switch.
|
||||
///
|
||||
/// Typically an [Icon] widget.
|
||||
final Widget secondary;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// If this property is null then its value is based on [ListTileTheme.dense].
|
||||
final bool dense;
|
||||
|
||||
/// 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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget control = new Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: activeColor,
|
||||
activeThumbImage: activeThumbImage,
|
||||
inactiveThumbImage: inactiveThumbImage,
|
||||
);
|
||||
return new MergeSemantics(
|
||||
child: ListTileTheme.merge(
|
||||
selectedColor: activeColor ?? Theme.of(context).accentColor,
|
||||
child: new ListTile(
|
||||
leading: secondary,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
trailing: control,
|
||||
isThreeLine: isThreeLine,
|
||||
dense: dense,
|
||||
enabled: onChanged != null,
|
||||
onTap: onChanged != null ? () { onChanged(!value); } : null,
|
||||
selected: selected,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import 'framework.dart';
|
||||
import 'media_query.dart';
|
||||
|
||||
export 'package:flutter/services.dart' show
|
||||
ImageProvider,
|
||||
AssetImage,
|
||||
ExactAssetImage,
|
||||
MemoryImage,
|
||||
|
@ -68,7 +68,7 @@ class DefaultTextStyle extends InheritedWidget {
|
||||
softWrap: softWrap ?? parent.softWrap,
|
||||
overflow: overflow ?? parent.overflow,
|
||||
maxLines: maxLines ?? parent.maxLines,
|
||||
child: child
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
118
packages/flutter/test/material/control_list_tile_test.dart
Normal file
118
packages/flutter/test/material/control_list_tile_test.dart
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('CheckboxListTile control test', (WidgetTester tester) async {
|
||||
final List<dynamic> log = <dynamic>[];
|
||||
await tester.pumpWidget(new Material(
|
||||
child: new CheckboxListTile(
|
||||
value: true,
|
||||
onChanged: (bool value) { log.add(value); },
|
||||
title: const Text('Hello'),
|
||||
),
|
||||
));
|
||||
await tester.tap(find.text('Hello'));
|
||||
log.add('-');
|
||||
await tester.tap(find.byType(Checkbox));
|
||||
expect(log, equals(<dynamic>[false, '-', false]));
|
||||
});
|
||||
|
||||
testWidgets('RadioListTile control test', (WidgetTester tester) async {
|
||||
final List<dynamic> log = <dynamic>[];
|
||||
await tester.pumpWidget(new Material(
|
||||
child: new RadioListTile<bool>(
|
||||
value: true,
|
||||
groupValue: false,
|
||||
onChanged: (bool value) { log.add(value); },
|
||||
title: const Text('Hello'),
|
||||
),
|
||||
));
|
||||
await tester.tap(find.text('Hello'));
|
||||
log.add('-');
|
||||
await tester.tap(find.byType(const Radio<bool>(value: false, groupValue: false, onChanged: null).runtimeType));
|
||||
expect(log, equals(<dynamic>[true, '-', true]));
|
||||
});
|
||||
|
||||
testWidgets('SwitchListTile control test', (WidgetTester tester) async {
|
||||
final List<dynamic> log = <dynamic>[];
|
||||
await tester.pumpWidget(new Material(
|
||||
child: new SwitchListTile(
|
||||
value: true,
|
||||
onChanged: (bool value) { log.add(value); },
|
||||
title: const Text('Hello'),
|
||||
),
|
||||
));
|
||||
await tester.tap(find.text('Hello'));
|
||||
log.add('-');
|
||||
await tester.tap(find.byType(Switch));
|
||||
expect(log, equals(<dynamic>[false, '-', false]));
|
||||
});
|
||||
|
||||
testWidgets('SwitchListTile control test', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(new Material(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new SwitchListTile(
|
||||
value: true,
|
||||
onChanged: (bool value) { },
|
||||
title: const Text('AAA'),
|
||||
secondary: const Text('aaa'),
|
||||
),
|
||||
new CheckboxListTile(
|
||||
value: true,
|
||||
onChanged: (bool value) { },
|
||||
title: const Text('BBB'),
|
||||
secondary: const Text('bbb'),
|
||||
),
|
||||
new RadioListTile<bool>(
|
||||
value: true,
|
||||
groupValue: false,
|
||||
onChanged: (bool value) { },
|
||||
title: const Text('CCC'),
|
||||
secondary: const Text('ccc'),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
// This test verifies that the label and the control get merged.
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
|
||||
transform: null,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
actions: SemanticsAction.tap.index,
|
||||
label: 'aaa\nAAA',
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 6,
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
|
||||
transform: new Matrix4.translationValues(0.0, 56.0, 0.0),
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
actions: SemanticsAction.tap.index,
|
||||
label: 'bbb\nBBB',
|
||||
),
|
||||
new TestSemantics.rootChild(
|
||||
id: 11,
|
||||
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
|
||||
transform: new Matrix4.translationValues(0.0, 112.0, 0.0),
|
||||
flags: SemanticsFlags.hasCheckedState.index,
|
||||
actions: SemanticsAction.tap.index,
|
||||
label: 'CCC\nccc',
|
||||
),
|
||||
],
|
||||
)));
|
||||
});
|
||||
|
||||
}
|
@ -195,7 +195,7 @@ class SemanticsTester {
|
||||
}
|
||||
|
||||
class _HasSemantics extends Matcher {
|
||||
const _HasSemantics(this._semantics);
|
||||
const _HasSemantics(this._semantics) : assert(_semantics != null);
|
||||
|
||||
final TestSemantics _semantics;
|
||||
|
||||
@ -211,26 +211,27 @@ class _HasSemantics extends Matcher {
|
||||
|
||||
@override
|
||||
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
|
||||
const String help = 'Try dumping the semantics with debugDumpSemanticsTree() from the rendering library to see what the semantics tree looks like.';
|
||||
final TestSemantics testNode = matchState[TestSemantics];
|
||||
final SemanticsNode node = matchState[SemanticsNode];
|
||||
if (node == null)
|
||||
return mismatchDescription.add('could not find node with id ${testNode.id}');
|
||||
return mismatchDescription.add('could not find node with id ${testNode.id}.\n$help');
|
||||
if (testNode.id != node.id)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}.\n$help');
|
||||
final SemanticsData data = node.getSemanticsData();
|
||||
if (testNode.flags != data.flags)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}.\n$help');
|
||||
if (testNode.actions != data.actions)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}.\n$help');
|
||||
if (testNode.label != data.label)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}"');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}".\n$help');
|
||||
if (testNode.rect != data.rect)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}.\n$help');
|
||||
if (testNode.transform != data.transform)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:\n${data.transform}');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:.\n${data.transform}.\n$help');
|
||||
final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount;
|
||||
if (testNode.children.length != childrenCount)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} but found $childrenCount children');
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} children but found $childrenCount.\n$help');
|
||||
return mismatchDescription;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user