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;
|
Timer? interactionTimer;
|
||||||
|
|
||||||
final GlobalKey _renderObjectKey = GlobalKey();
|
final GlobalKey _renderObjectKey = GlobalKey();
|
||||||
|
|
||||||
// Keyboard mapping for a focused slider.
|
// 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.arrowUp): _AdjustSliderIntent.up(),
|
||||||
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
|
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
|
||||||
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
|
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
|
||||||
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
|
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.
|
// Action mapping for a focused slider.
|
||||||
late Map<Type, Action<Intent>> _actionMap;
|
late Map<Type, Action<Intent>> _actionMap;
|
||||||
|
|
||||||
@ -735,13 +744,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
|
|||||||
break;
|
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(
|
return Semantics(
|
||||||
container: true,
|
container: true,
|
||||||
slider: true,
|
slider: true,
|
||||||
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
|
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
|
||||||
child: FocusableActionDetector(
|
child: FocusableActionDetector(
|
||||||
actions: _actionMap,
|
actions: _actionMap,
|
||||||
shortcuts: _shortcutMap,
|
shortcuts: shortcutMap,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
autofocus: widget.autofocus,
|
autofocus: widget.autofocus,
|
||||||
enabled: _enabled,
|
enabled: _enabled,
|
||||||
|
@ -2154,6 +2154,102 @@ void main() {
|
|||||||
expect(value, 0.5);
|
expect(value, 0.5);
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
}, 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 {
|
testWidgets('Slider gains keyboard focus when it gains semantics focus on Windows', (WidgetTester tester) async {
|
||||||
final SemanticsTester semantics = SemanticsTester(tester);
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
|
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user