Updating the Slider Widget to allow up and down arrow keys to navigate out of the slider when in directional NavigationMode. (#103149)
This commit is contained in:
parent
2f657536c8
commit
ae7fcc7e51
@ -475,13 +475,22 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
|
||||
Timer? interactionTimer;
|
||||
|
||||
final GlobalKey _renderObjectKey = GlobalKey();
|
||||
|
||||
// Keyboard mapping for a focused slider.
|
||||
final Map<ShortcutActivator, Intent> _shortcutMap = const <ShortcutActivator, Intent>{
|
||||
static const Map<ShortcutActivator, Intent> _traditionalNavShortcutMap = <ShortcutActivator, Intent>{
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp): _AdjustSliderIntent.up(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
|
||||
};
|
||||
|
||||
// Keyboard mapping for a focused slider when using directional navigation.
|
||||
// The vertical inputs are not handled to allow navigating out of the slider.
|
||||
static const Map<ShortcutActivator, Intent> _directionalNavShortcutMap = <ShortcutActivator, Intent>{
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
|
||||
};
|
||||
|
||||
// Action mapping for a focused slider.
|
||||
late Map<Type, Action<Intent>> _actionMap;
|
||||
|
||||
@ -735,13 +744,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
|
||||
break;
|
||||
}
|
||||
|
||||
final Map<ShortcutActivator, Intent> shortcutMap;
|
||||
switch (MediaQuery.of(context).navigationMode) {
|
||||
case NavigationMode.directional:
|
||||
shortcutMap = _directionalNavShortcutMap;
|
||||
break;
|
||||
case NavigationMode.traditional:
|
||||
shortcutMap = _traditionalNavShortcutMap;
|
||||
break;
|
||||
}
|
||||
|
||||
return Semantics(
|
||||
container: true,
|
||||
slider: true,
|
||||
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
|
||||
child: FocusableActionDetector(
|
||||
actions: _actionMap,
|
||||
shortcuts: _shortcutMap,
|
||||
shortcuts: shortcutMap,
|
||||
focusNode: focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
enabled: _enabled,
|
||||
|
@ -2154,6 +2154,102 @@ void main() {
|
||||
expect(value, 0.5);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('In directional nav, Slider can be navigated out of by using up and down arrows', (WidgetTester tester) async {
|
||||
const Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up),
|
||||
};
|
||||
|
||||
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
||||
double topSliderValue = 0.5;
|
||||
double bottomSliderValue = 0.5;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Shortcuts(
|
||||
shortcuts: shortcuts,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
||||
return MediaQuery(
|
||||
data: const MediaQueryData(navigationMode: NavigationMode.directional),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Slider(
|
||||
value: topSliderValue,
|
||||
onChanged: (double newValue) {
|
||||
setState(() {
|
||||
topSliderValue = newValue;
|
||||
});
|
||||
},
|
||||
autofocus: true,
|
||||
),
|
||||
Slider(
|
||||
value: bottomSliderValue,
|
||||
onChanged: (double newValue) {
|
||||
setState(() {
|
||||
bottomSliderValue = newValue;
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The top slider is auto-focused and can be adjusted with left and right arrow keys.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.55, reason: 'focused top Slider increased after first arrowRight');
|
||||
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by first arrowRight');
|
||||
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.5, reason: 'focused top Slider decreased after first arrowLeft');
|
||||
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by first arrowLeft');
|
||||
|
||||
// Pressing the down-arrow key moves focus down to the bottom slider
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.5, reason: 'arrowDown unfocuses top Slider, does not alter its value');
|
||||
expect(bottomSliderValue, 0.5, reason: 'arrowDown focuses bottom Slider, does not alter its value');
|
||||
|
||||
// The bottom slider is now focused and can be adjusted with left and right arrow keys.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.5, reason: 'unfocused top Slider unaffected by second arrowRight');
|
||||
expect(bottomSliderValue, 0.55, reason: 'focused bottom Slider increased by second arrowRight');
|
||||
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.5, reason: 'unfocused top Slider unaffected by second arrowLeft');
|
||||
expect(bottomSliderValue, 0.5, reason: 'focused bottom Slider decreased by second arrowLeft');
|
||||
|
||||
// Pressing the up-arrow key moves focus back up to the top slider
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.5, reason: 'arrowUp focuses top Slider, does not alter its value');
|
||||
expect(bottomSliderValue, 0.5, reason: 'arrowUp unfocuses bottom Slider, does not alter its value');
|
||||
|
||||
// The top slider is now focused again and can be adjusted with left and right arrow keys.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.55, reason: 'focused top Slider increased after third arrowRight');
|
||||
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by third arrowRight');
|
||||
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||
await tester.pumpAndSettle();
|
||||
expect(topSliderValue, 0.5, reason: 'focused top Slider decreased after third arrowRight');
|
||||
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by third arrowRight');
|
||||
});
|
||||
|
||||
testWidgets('Slider gains keyboard focus when it gains semantics focus on Windows', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
|
||||
|
Loading…
x
Reference in New Issue
Block a user