Unverified Commit d64955ab authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add visualDensity and focus support to ListTile (#53888)

parent de84c1e1
...@@ -252,7 +252,10 @@ class _OptionsState extends State<Options> { ...@@ -252,7 +252,10 @@ class _OptionsState extends State<Options> {
min: VisualDensity.minimumDensity, min: VisualDensity.minimumDensity,
max: VisualDensity.maximumDensity, max: VisualDensity.maximumDensity,
onChanged: (double value) { onChanged: (double value) {
widget.model.density = widget.model.density.copyWith(horizontal: value, vertical: widget.model.density.vertical); widget.model.density = widget.model.density.copyWith(
horizontal: value,
vertical: widget.model.density.vertical,
);
}, },
value: widget.model.density.horizontal, value: widget.model.density.horizontal,
), ),
...@@ -278,7 +281,10 @@ class _OptionsState extends State<Options> { ...@@ -278,7 +281,10 @@ class _OptionsState extends State<Options> {
min: VisualDensity.minimumDensity, min: VisualDensity.minimumDensity,
max: VisualDensity.maximumDensity, max: VisualDensity.maximumDensity,
onChanged: (double value) { onChanged: (double value) {
widget.model.density = widget.model.density.copyWith(horizontal: widget.model.density.horizontal, vertical: value); widget.model.density = widget.model.density.copyWith(
horizontal: widget.model.density.horizontal,
vertical: value,
);
}, },
value: widget.model.density.vertical, value: widget.model.density.vertical,
), ),
...@@ -376,7 +382,13 @@ class _ControlTile extends StatelessWidget { ...@@ -376,7 +382,13 @@ class _ControlTile extends StatelessWidget {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Align(alignment: AlignmentDirectional.topStart, child: Text(label, textAlign: TextAlign.start)), Align(
alignment: AlignmentDirectional.topStart,
child: Text(
label,
textAlign: TextAlign.start,
),
),
child, child,
], ],
), ),
...@@ -419,9 +431,59 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -419,9 +431,59 @@ class _MyHomePageState extends State<MyHomePage> {
primarySwatch: m2Swatch, primarySwatch: m2Swatch,
); );
final Widget label = Text(_model.rtl ? 'اضغط علي' : 'Press Me'); final Widget label = Text(_model.rtl ? 'اضغط علي' : 'Press Me');
textController.text = _model.rtl ? 'يعتمد القرار الجيد على المعرفة وليس على الأرقام.' : 'A good decision is based on knowledge and not on numbers.'; textController.text = _model.rtl
? 'يعتمد القرار الجيد على المعرفة وليس على الأرقام.'
: 'A good decision is based on knowledge and not on numbers.';
final List<Widget> tiles = <Widget>[ final List<Widget> tiles = <Widget>[
_ControlTile(
label: _model.rtl ? 'حقل النص' : 'List Tile',
child: SizedBox(
width: 400,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListTile(
title: Text(_model.rtl ? 'هذا عنوان طويل نسبيا' : 'This is a relatively long title'),
onTap: () {},
),
ListTile(
title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'),
subtitle:
Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'),
trailing: Icon(Icons.check_box),
onTap: () {},
),
ListTile(
title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'),
subtitle:
Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'),
leading: Icon(Icons.check_box),
dense: true,
onTap: () {},
),
ListTile(
title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'),
subtitle:
Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'),
dense: true,
leading: Icon(Icons.add_box),
trailing: Icon(Icons.check_box),
onTap: () {},
),
ListTile(
title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'),
subtitle:
Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'),
isThreeLine: true,
leading: Icon(Icons.add_box),
trailing: Icon(Icons.check_box),
onTap: () {},
),
],
),
),
),
_ControlTile( _ControlTile(
label: _model.rtl ? 'حقل النص' : 'Text Field', label: _model.rtl ? 'حقل النص' : 'Text Field',
child: SizedBox( child: SizedBox(
......
...@@ -14,6 +14,7 @@ import 'debug.dart'; ...@@ -14,6 +14,7 @@ import 'debug.dart';
import 'divider.dart'; import 'divider.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart';
/// Defines the title font used for [ListTile] descendants of a [ListTileTheme]. /// Defines the title font used for [ListTile] descendants of a [ListTileTheme].
/// ///
...@@ -633,11 +634,15 @@ class ListTile extends StatelessWidget { ...@@ -633,11 +634,15 @@ class ListTile extends StatelessWidget {
this.trailing, this.trailing,
this.isThreeLine = false, this.isThreeLine = false,
this.dense, this.dense,
this.visualDensity,
this.contentPadding, this.contentPadding,
this.enabled = true, this.enabled = true,
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
this.selected = false, this.selected = false,
this.focusColor,
this.hoverColor,
this.focusNode,
this.autofocus = false, this.autofocus = false,
}) : assert(isThreeLine != null), }) : assert(isThreeLine != null),
assert(enabled != null), assert(enabled != null),
...@@ -695,6 +700,16 @@ class ListTile extends StatelessWidget { ...@@ -695,6 +700,16 @@ class ListTile extends StatelessWidget {
/// Dense list tiles default to a smaller height. /// Dense list tiles default to a smaller height.
final bool dense; final bool dense;
/// Defines how compact the list tile's layout will be.
///
/// {@macro flutter.material.themedata.visualDensity}
///
/// See also:
///
/// * [ThemeData.visualDensity], which specifies the [density] for all widgets
/// within a [Theme].
final VisualDensity visualDensity;
/// The tile's internal padding. /// The tile's internal padding.
/// ///
/// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle],
...@@ -726,6 +741,15 @@ class ListTile extends StatelessWidget { ...@@ -726,6 +741,15 @@ class ListTile extends StatelessWidget {
/// can be overridden with a [ListTileTheme]. /// can be overridden with a [ListTileTheme].
final bool selected; final bool selected;
/// The color for the tile's [Material] when it has the input focus.
final Color focusColor;
/// The color for the tile's [Material] when a pointer is hovering over it.
final Color hoverColor;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus} /// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus; final bool autofocus;
...@@ -888,6 +912,9 @@ class ListTile extends StatelessWidget { ...@@ -888,6 +912,9 @@ class ListTile extends StatelessWidget {
onTap: enabled ? onTap : null, onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null, onLongPress: enabled ? onLongPress : null,
canRequestFocus: enabled, canRequestFocus: enabled,
focusNode: focusNode,
focusColor: focusColor,
hoverColor: hoverColor,
autofocus: autofocus, autofocus: autofocus,
child: Semantics( child: Semantics(
selected: selected, selected: selected,
...@@ -902,6 +929,7 @@ class ListTile extends StatelessWidget { ...@@ -902,6 +929,7 @@ class ListTile extends StatelessWidget {
subtitle: subtitleText, subtitle: subtitleText,
trailing: trailingIcon, trailing: trailingIcon,
isDense: _isDenseLayout(tileTheme), isDense: _isDenseLayout(tileTheme),
visualDensity: visualDensity ?? theme.visualDensity,
isThreeLine: isThreeLine, isThreeLine: isThreeLine,
textDirection: textDirection, textDirection: textDirection,
titleBaselineType: titleStyle.textBaseline, titleBaselineType: titleStyle.textBaseline,
...@@ -930,11 +958,13 @@ class _ListTile extends RenderObjectWidget { ...@@ -930,11 +958,13 @@ class _ListTile extends RenderObjectWidget {
this.trailing, this.trailing,
@required this.isThreeLine, @required this.isThreeLine,
@required this.isDense, @required this.isDense,
@required this.visualDensity,
@required this.textDirection, @required this.textDirection,
@required this.titleBaselineType, @required this.titleBaselineType,
this.subtitleBaselineType, this.subtitleBaselineType,
}) : assert(isThreeLine != null), }) : assert(isThreeLine != null),
assert(isDense != null), assert(isDense != null),
assert(visualDensity != null),
assert(textDirection != null), assert(textDirection != null),
assert(titleBaselineType != null), assert(titleBaselineType != null),
super(key: key); super(key: key);
...@@ -945,6 +975,7 @@ class _ListTile extends RenderObjectWidget { ...@@ -945,6 +975,7 @@ class _ListTile extends RenderObjectWidget {
final Widget trailing; final Widget trailing;
final bool isThreeLine; final bool isThreeLine;
final bool isDense; final bool isDense;
final VisualDensity visualDensity;
final TextDirection textDirection; final TextDirection textDirection;
final TextBaseline titleBaselineType; final TextBaseline titleBaselineType;
final TextBaseline subtitleBaselineType; final TextBaseline subtitleBaselineType;
...@@ -957,6 +988,7 @@ class _ListTile extends RenderObjectWidget { ...@@ -957,6 +988,7 @@ class _ListTile extends RenderObjectWidget {
return _RenderListTile( return _RenderListTile(
isThreeLine: isThreeLine, isThreeLine: isThreeLine,
isDense: isDense, isDense: isDense,
visualDensity: visualDensity,
textDirection: textDirection, textDirection: textDirection,
titleBaselineType: titleBaselineType, titleBaselineType: titleBaselineType,
subtitleBaselineType: subtitleBaselineType, subtitleBaselineType: subtitleBaselineType,
...@@ -968,6 +1000,7 @@ class _ListTile extends RenderObjectWidget { ...@@ -968,6 +1000,7 @@ class _ListTile extends RenderObjectWidget {
renderObject renderObject
..isThreeLine = isThreeLine ..isThreeLine = isThreeLine
..isDense = isDense ..isDense = isDense
..visualDensity = visualDensity
..textDirection = textDirection ..textDirection = textDirection
..titleBaselineType = titleBaselineType ..titleBaselineType = titleBaselineType
..subtitleBaselineType = subtitleBaselineType; ..subtitleBaselineType = subtitleBaselineType;
...@@ -1091,15 +1124,18 @@ class _ListTileElement extends RenderObjectElement { ...@@ -1091,15 +1124,18 @@ class _ListTileElement extends RenderObjectElement {
class _RenderListTile extends RenderBox { class _RenderListTile extends RenderBox {
_RenderListTile({ _RenderListTile({
@required bool isDense, @required bool isDense,
@required VisualDensity visualDensity,
@required bool isThreeLine, @required bool isThreeLine,
@required TextDirection textDirection, @required TextDirection textDirection,
@required TextBaseline titleBaselineType, @required TextBaseline titleBaselineType,
TextBaseline subtitleBaselineType, TextBaseline subtitleBaselineType,
}) : assert(isDense != null), }) : assert(isDense != null),
assert(visualDensity != null),
assert(isThreeLine != null), assert(isThreeLine != null),
assert(textDirection != null), assert(textDirection != null),
assert(titleBaselineType != null), assert(titleBaselineType != null),
_isDense = isDense, _isDense = isDense,
_visualDensity = visualDensity,
_isThreeLine = isThreeLine, _isThreeLine = isThreeLine,
_textDirection = textDirection, _textDirection = textDirection,
_titleBaselineType = titleBaselineType, _titleBaselineType = titleBaselineType,
...@@ -1107,7 +1143,7 @@ class _RenderListTile extends RenderBox { ...@@ -1107,7 +1143,7 @@ class _RenderListTile extends RenderBox {
static const double _minLeadingWidth = 40.0; static const double _minLeadingWidth = 40.0;
// The horizontal gap between the titles and the leading/trailing widgets // The horizontal gap between the titles and the leading/trailing widgets
static const double _horizontalTitleGap = 16.0; double get _horizontalTitleGap => 16.0 + visualDensity.horizontal * 2.0;
// The minimum padding on the top and bottom of the title and subtitle widgets. // The minimum padding on the top and bottom of the title and subtitle widgets.
static const double _minVerticalPadding = 4.0; static const double _minVerticalPadding = 4.0;
...@@ -1174,6 +1210,16 @@ class _RenderListTile extends RenderBox { ...@@ -1174,6 +1210,16 @@ class _RenderListTile extends RenderBox {
markNeedsLayout(); markNeedsLayout();
} }
VisualDensity get visualDensity => _visualDensity;
VisualDensity _visualDensity;
set visualDensity(VisualDensity value) {
assert(value != null);
if (_visualDensity == value)
return;
_visualDensity = value;
markNeedsLayout();
}
bool get isThreeLine => _isThreeLine; bool get isThreeLine => _isThreeLine;
bool _isThreeLine; bool _isThreeLine;
set isThreeLine(bool value) { set isThreeLine(bool value) {
...@@ -1287,11 +1333,12 @@ class _RenderListTile extends RenderBox { ...@@ -1287,11 +1333,12 @@ class _RenderListTile extends RenderBox {
final bool isTwoLine = !isThreeLine && hasSubtitle; final bool isTwoLine = !isThreeLine && hasSubtitle;
final bool isOneLine = !isThreeLine && !hasSubtitle; final bool isOneLine = !isThreeLine && !hasSubtitle;
final Offset baseDensity = visualDensity.baseSizeAdjustment;
if (isOneLine) if (isOneLine)
return isDense ? 48.0 : 56.0; return (isDense ? 48.0 : 56.0) + baseDensity.dy;
if (isTwoLine) if (isTwoLine)
return isDense ? 64.0 : 72.0; return (isDense ? 64.0 : 72.0) + baseDensity.dy;
return isDense ? 76.0 : 88.0; return (isDense ? 76.0 : 88.0) + baseDensity.dy;
} }
@override @override
...@@ -1340,6 +1387,7 @@ class _RenderListTile extends RenderBox { ...@@ -1340,6 +1387,7 @@ class _RenderListTile extends RenderBox {
final bool hasTrailing = trailing != null; final bool hasTrailing = trailing != null;
final bool isTwoLine = !isThreeLine && hasSubtitle; final bool isTwoLine = !isThreeLine && hasSubtitle;
final bool isOneLine = !isThreeLine && !hasSubtitle; final bool isOneLine = !isThreeLine && !hasSubtitle;
final Offset densityAdjustment = visualDensity.baseSizeAdjustment;
final BoxConstraints maxIconHeightConstraint = BoxConstraints( final BoxConstraints maxIconHeightConstraint = BoxConstraints(
// One-line trailing and leading widget heights do not follow // One-line trailing and leading widget heights do not follow
...@@ -1347,7 +1395,7 @@ class _RenderListTile extends RenderBox { ...@@ -1347,7 +1395,7 @@ class _RenderListTile extends RenderBox {
// to accessibility requirements for smallest tappable widget. // to accessibility requirements for smallest tappable widget.
// Two- and three-line trailing widget heights are constrained // Two- and three-line trailing widget heights are constrained
// properly according to the Material spec. // properly according to the Material spec.
maxHeight: isDense ? 48.0 : 56.0, maxHeight: (isDense ? 48.0 : 56.0) + densityAdjustment.dy,
); );
final BoxConstraints looseConstraints = constraints.loosen(); final BoxConstraints looseConstraints = constraints.loosen();
final BoxConstraints iconConstraints = looseConstraints.enforce(maxIconHeightConstraint); final BoxConstraints iconConstraints = looseConstraints.enforce(maxIconHeightConstraint);
...@@ -1367,8 +1415,11 @@ class _RenderListTile extends RenderBox { ...@@ -1367,8 +1415,11 @@ class _RenderListTile extends RenderBox {
final double titleStart = hasLeading final double titleStart = hasLeading
? math.max(_minLeadingWidth, leadingSize.width) + _horizontalTitleGap ? math.max(_minLeadingWidth, leadingSize.width) + _horizontalTitleGap
: 0.0; : 0.0;
final double adjustedTrailingWidth = hasTrailing
? math.max(trailingSize.width + _horizontalTitleGap, 32.0)
: 0.0;
final BoxConstraints textConstraints = looseConstraints.tighten( final BoxConstraints textConstraints = looseConstraints.tighten(
width: tileWidth - titleStart - (hasTrailing ? trailingSize.width + _horizontalTitleGap : 0.0), width: tileWidth - titleStart - adjustedTrailingWidth,
); );
final Size titleSize = _layoutBox(title, textConstraints); final Size titleSize = _layoutBox(title, textConstraints);
final Size subtitleSize = _layoutBox(subtitle, textConstraints); final Size subtitleSize = _layoutBox(subtitle, textConstraints);
...@@ -1396,7 +1447,7 @@ class _RenderListTile extends RenderBox { ...@@ -1396,7 +1447,7 @@ class _RenderListTile extends RenderBox {
} else { } else {
assert(subtitleBaselineType != null); assert(subtitleBaselineType != null);
titleY = titleBaseline - _boxBaseline(title, titleBaselineType); titleY = titleBaseline - _boxBaseline(title, titleBaselineType);
subtitleY = subtitleBaseline - _boxBaseline(subtitle, subtitleBaselineType); subtitleY = subtitleBaseline - _boxBaseline(subtitle, subtitleBaselineType) + visualDensity.vertical * 2.0;
tileHeight = defaultTileHeight; tileHeight = defaultTileHeight;
// If the title and subtitle overlap, move the title upwards by half // If the title and subtitle overlap, move the title upwards by half
...@@ -1442,10 +1493,9 @@ class _RenderListTile extends RenderBox { ...@@ -1442,10 +1493,9 @@ class _RenderListTile extends RenderBox {
case TextDirection.rtl: { case TextDirection.rtl: {
if (hasLeading) if (hasLeading)
_positionBox(leading, Offset(tileWidth - leadingSize.width, leadingY)); _positionBox(leading, Offset(tileWidth - leadingSize.width, leadingY));
final double titleX = hasTrailing ? trailingSize.width + _horizontalTitleGap : 0.0; _positionBox(title, Offset(adjustedTrailingWidth, titleY));
_positionBox(title, Offset(titleX, titleY));
if (hasSubtitle) if (hasSubtitle)
_positionBox(subtitle, Offset(titleX, subtitleY)); _positionBox(subtitle, Offset(adjustedTrailingWidth, subtitleY));
if (hasTrailing) if (hasTrailing)
_positionBox(trailing, Offset(0.0, trailingY)); _positionBox(trailing, Offset(0.0, trailingY));
break; break;
......
...@@ -3,11 +3,15 @@ ...@@ -3,11 +3,15 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
class TestIcon extends StatefulWidget { class TestIcon extends StatefulWidget {
...@@ -1231,4 +1235,212 @@ void main() { ...@@ -1231,4 +1235,212 @@ void main() {
await tester.pump(); await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse); expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse);
}); });
testWidgets('ListTile is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'ListTile');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Key tileKey = Key('listTile');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Container(
width: 100,
height: 100,
color: Colors.white,
child: ListTile(
key: tileKey,
onTap: enabled ? () {} : null,
focusColor: Colors.orange[500],
autofocus: true,
focusNode: focusNode,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byKey(tileKey))),
paints
..rect(
color: Colors.orange[500],
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
)
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0),
),
);
// Check when the list tile is disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byKey(tileKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0)),
);
});
testWidgets('ListTile can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Key tileKey = Key('ListTile');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Container(
width: 100,
height: 100,
color: Colors.white,
child: ListTile(
key: tileKey,
onTap: enabled ? () {} : null,
hoverColor: Colors.orange[500],
autofocus: true,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pump();
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(tileKey))),
paints
..rect(
color: const Color(0x1f000000),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0)),
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byKey(tileKey)));
await tester.pumpWidget(buildApp());
await tester.pump();
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(tileKey))),
paints
..rect(
color: const Color(0x1f000000),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..rect(
color: Colors.orange[500],
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0)),
);
await tester.pumpWidget(buildApp(enabled: false));
await tester.pump();
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(tileKey))),
paints
..rect(
color: Colors.orange[500],
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0)),
);
});
testWidgets('ListTile can be triggerd by keyboard shortcuts', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Key tileKey = Key('ListTile');
bool tapped = false;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Container(
width: 200,
height: 100,
color: Colors.white,
child: ListTile(
key: tileKey,
onTap: enabled ? () {
setState((){
tapped = true;
});
} : null,
hoverColor: Colors.orange[500],
autofocus: true,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(tapped, isTrue);
});
testWidgets('ListTile responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
Future<void> buildTest(VisualDensity visualDensity) async {
return await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: ListTile(
key: key,
onTap: () {},
autofocus: true,
visualDensity: visualDensity,
),
),
),
),
);
}
await buildTest(const VisualDensity());
final RenderBox box = tester.renderObject(find.byKey(key));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(800, 56)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(800, 68)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(800, 44)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(800, 44)));
});
} }
...@@ -418,7 +418,7 @@ void main() { ...@@ -418,7 +418,7 @@ void main() {
); );
}); });
testWidgets('Radio can be hovered and has correct focus color', (WidgetTester tester) async { testWidgets('Radio can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
int groupValue = 0; int groupValue = 0;
const Key radioKey = Key('radio'); const Key radioKey = Key('radio');
...@@ -479,7 +479,7 @@ void main() { ...@@ -479,7 +479,7 @@ void main() {
color: const Color(0xffffffff), color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0)) rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: Colors.orange[500]) ..circle(color: Colors.orange[500])
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0) ..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0),
); );
// Check when the radio is selected, but disabled. // Check when the radio is selected, but disabled.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment