diff --git a/packages/flutter/lib/src/material/checkbox_list_tile.dart b/packages/flutter/lib/src/material/checkbox_list_tile.dart index e684cfc182..410980423c 100644 --- a/packages/flutter/lib/src/material/checkbox_list_tile.dart +++ b/packages/flutter/lib/src/material/checkbox_list_tile.dart @@ -266,11 +266,13 @@ class CheckboxListTile extends StatelessWidget { this.secondary, this.selected = false, this.controlAffinity = ListTileControlAffinity.platform, + this.autofocus = false, }) : assert(value != null), assert(isThreeLine != null), assert(!isThreeLine || subtitle != null), assert(selected != null), assert(controlAffinity != null), + assert(autofocus != null), super(key: key); /// Whether this checkbox is checked. @@ -351,6 +353,9 @@ class CheckboxListTile extends StatelessWidget { /// Where to place the control relative to the text. final ListTileControlAffinity controlAffinity; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + @override Widget build(BuildContext context) { final Widget control = Checkbox( @@ -359,6 +364,7 @@ class CheckboxListTile extends StatelessWidget { activeColor: activeColor, checkColor: checkColor, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + autofocus: autofocus, ); Widget leading, trailing; switch (controlAffinity) { @@ -385,6 +391,7 @@ class CheckboxListTile extends StatelessWidget { enabled: onChanged != null, onTap: onChanged != null ? () { onChanged(!value); } : null, selected: selected, + autofocus: autofocus, ), ), ); diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index e5562924c7..9a636c5eaa 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -638,9 +638,11 @@ class ListTile extends StatelessWidget { this.onTap, this.onLongPress, this.selected = false, + this.autofocus = false, }) : assert(isThreeLine != null), assert(enabled != null), assert(selected != null), + assert(autofocus != null), assert(!isThreeLine || subtitle != null), super(key: key); @@ -724,6 +726,9 @@ class ListTile extends StatelessWidget { /// can be overridden with a [ListTileTheme]. final bool selected; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// Add a one pixel border in between each tile. If color isn't specified the /// [ThemeData.dividerColor] of the context's [Theme] is used. /// @@ -883,6 +888,7 @@ class ListTile extends StatelessWidget { onTap: enabled ? onTap : null, onLongPress: enabled ? onLongPress : null, canRequestFocus: enabled, + autofocus: autofocus, child: Semantics( selected: selected, enabled: enabled, diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index 6a56ad28df..50274f643d 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -318,12 +318,14 @@ class RadioListTile extends StatelessWidget { this.secondary, this.selected = false, this.controlAffinity = ListTileControlAffinity.platform, + this.autofocus = false, }) : assert(toggleable != null), assert(isThreeLine != null), assert(!isThreeLine || subtitle != null), assert(selected != null), assert(controlAffinity != null), + assert(autofocus != null), super(key: key); /// The value represented by this radio button. @@ -464,6 +466,9 @@ class RadioListTile extends StatelessWidget { /// Where to place the control relative to the text. final ListTileControlAffinity controlAffinity; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// Whether this radio button is checked. /// /// To control this value, set [value] and [groupValue] appropriately. @@ -478,6 +483,7 @@ class RadioListTile extends StatelessWidget { toggleable: toggleable, activeColor: activeColor, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + autofocus: autofocus, ); Widget leading, trailing; switch (controlAffinity) { @@ -512,6 +518,7 @@ class RadioListTile extends StatelessWidget { } } : null, selected: selected, + autofocus: autofocus, ), ), ); diff --git a/packages/flutter/lib/src/material/switch_list_tile.dart b/packages/flutter/lib/src/material/switch_list_tile.dart index e3d8a136a6..62fae85df7 100644 --- a/packages/flutter/lib/src/material/switch_list_tile.dart +++ b/packages/flutter/lib/src/material/switch_list_tile.dart @@ -272,11 +272,13 @@ class SwitchListTile extends StatelessWidget { this.contentPadding, this.secondary, this.selected = false, + this.autofocus = false, }) : _switchListTileType = _SwitchListTileType.material, assert(value != null), assert(isThreeLine != null), assert(!isThreeLine || subtitle != null), assert(selected != null), + assert(autofocus != null), super(key: key); /// Creates the wrapped switch with [Switch.adaptive]. @@ -304,11 +306,13 @@ class SwitchListTile extends StatelessWidget { this.contentPadding, this.secondary, this.selected = false, + this.autofocus = false, }) : _switchListTileType = _SwitchListTileType.adaptive, assert(value != null), assert(isThreeLine != null), assert(!isThreeLine || subtitle != null), assert(selected != null), + assert(autofocus != null), super(key: key); /// Whether this switch is checked. @@ -419,6 +423,9 @@ class SwitchListTile extends StatelessWidget { /// Normally, this property is left to its default value, false. final bool selected; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// If adaptive, creates the switch with [Switch.adaptive]. final _SwitchListTileType _switchListTileType; @@ -437,6 +444,7 @@ class SwitchListTile extends StatelessWidget { activeTrackColor: activeTrackColor, inactiveTrackColor: inactiveTrackColor, inactiveThumbColor: inactiveThumbColor, + autofocus: autofocus, ); break; @@ -451,6 +459,7 @@ class SwitchListTile extends StatelessWidget { activeTrackColor: activeTrackColor, inactiveTrackColor: inactiveTrackColor, inactiveThumbColor: inactiveThumbColor, + autofocus: autofocus, ); } return MergeSemantics( @@ -467,6 +476,7 @@ class SwitchListTile extends StatelessWidget { enabled: onChanged != null, onTap: onChanged != null ? () { onChanged(!value); } : null, selected: selected, + autofocus: autofocus, ), ), ); diff --git a/packages/flutter/test/material/checkbox_list_tile_test.dart b/packages/flutter/test/material/checkbox_list_tile_test.dart index cde81ebd4c..18fa5746a3 100644 --- a/packages/flutter/test/material/checkbox_list_tile_test.dart +++ b/packages/flutter/test/material/checkbox_list_tile_test.dart @@ -82,4 +82,37 @@ void main() { await tester.pumpAndSettle(); expect(getCheckboxListTileRenderer(), paints..rrect(color: const Color(0xFFFFFFFF))); // paints's color is 0xFFFFFFFF (params) }); + + testWidgets('CheckboxListTile can autofocus unless disabled.', (WidgetTester tester) async { + final GlobalKey childKey = GlobalKey(); + + await tester.pumpWidget( + wrap( + child: CheckboxListTile( + value: true, + onChanged: (_) {}, + title: Text('Hello', key: childKey), + autofocus: true, + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue); + + await tester.pumpWidget( + wrap( + child: CheckboxListTile( + value: true, + onChanged: null, + title: Text('Hello', key: childKey), + autofocus: true, + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse); + }); + } diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index d737320345..c447c02fae 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -1137,6 +1137,7 @@ void main() { expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 16.0, 24.0, 56.0)); expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 88.0 + 16.0, 24.0, 56.0)); }); + testWidgets('ListTile only accepts focus when enabled', (WidgetTester tester) async { final GlobalKey childKey = GlobalKey(); @@ -1184,4 +1185,50 @@ void main() { expect(tester.binding.focusManager.primaryFocus, isNot(equals(tileNode))); expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse); }); + + testWidgets('ListTile can autofocus unless disabled.', (WidgetTester tester) async { + final GlobalKey childKey = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: ListView( + children: [ + ListTile( + title: Text('A', key: childKey), + dense: true, + enabled: true, + autofocus: true, + onTap: () {}, + ), + ], + ), + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: ListView( + children: [ + ListTile( + title: Text('A', key: childKey), + dense: true, + enabled: false, + autofocus: true, + onTap: () {}, + ), + ], + ), + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse); + }); } diff --git a/packages/flutter/test/material/radio_list_tile_test.dart b/packages/flutter/test/material/radio_list_tile_test.dart index fd76a03fe1..3b03afac52 100644 --- a/packages/flutter/test/material/radio_list_tile_test.dart +++ b/packages/flutter/test/material/radio_list_tile_test.dart @@ -572,4 +572,38 @@ void main() { semantics.dispose(); SystemChannels.accessibility.setMockMessageHandler(null); }); + + testWidgets('RadioListTile can autofocus unless disabled.', (WidgetTester tester) async { + final GlobalKey childKey = GlobalKey(); + + await tester.pumpWidget( + wrap( + child: RadioListTile( + value: 1, + groupValue: 2, + onChanged: (_) {}, + title: Text('Title', key: childKey), + autofocus: true, + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue); + + await tester.pumpWidget( + wrap( + child: RadioListTile( + value: 1, + groupValue: 2, + onChanged: null, + title: Text('Title', key: childKey), + autofocus: true, + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse); + }); } diff --git a/packages/flutter/test/material/switch_list_tile_test.dart b/packages/flutter/test/material/switch_list_tile_test.dart index 4769d6b8a4..a4ad456bcd 100644 --- a/packages/flutter/test/material/switch_list_tile_test.dart +++ b/packages/flutter/test/material/switch_list_tile_test.dart @@ -254,4 +254,48 @@ void main() { expect(tester.getTopLeft(find.byType(Switch)).dx, 20.0); // contentPadding.end = 20 expect(tester.getTopRight(find.text('L')).dx, 790.0); // 800 - contentPadding.start }); + + testWidgets('SwitchListTile can autofocus unless disabled.', (WidgetTester tester) async { + final GlobalKey childKey = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: ListView( + children: [ + SwitchListTile( + value: true, + onChanged: (_) {}, + title: Text('A', key: childKey), + autofocus: true, + ), + ], + ), + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: ListView( + children: [ + SwitchListTile( + value: true, + onChanged: null, + title: Text('A', key: childKey), + autofocus: true, + ), + ], + ), + ), + ), + ); + + await tester.pump(); + expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse); + }); }