Add focus detector to CupertinoSwitch (#118345)
* Add focus detector to CupertinoSwitch * Add comment * Remove whitespace * Add focusColor constructor to CupertinoSwitch * Remove whitespace * Add color type * Remove gap in border * Adjust color and line thickness
This commit is contained in:
parent
b9ab640491
commit
ea36b3a5a7
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
@ -74,6 +74,7 @@ class CupertinoSwitch extends StatefulWidget {
|
||||
this.trackColor,
|
||||
this.thumbColor,
|
||||
this.applyTheme,
|
||||
this.focusColor,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
}) : assert(value != null),
|
||||
assert(dragStartBehavior != null);
|
||||
@ -125,6 +126,11 @@ class CupertinoSwitch extends StatefulWidget {
|
||||
/// Defaults to [CupertinoColors.white] when null.
|
||||
final Color? thumbColor;
|
||||
|
||||
/// The color to use for the focus highlight for keyboard interactions.
|
||||
///
|
||||
/// Defaults to a a slightly transparent [activeColor].
|
||||
final Color? focusColor;
|
||||
|
||||
/// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
|
||||
/// Whether to apply the ambient [CupertinoThemeData].
|
||||
///
|
||||
@ -178,8 +184,14 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
|
||||
late AnimationController _reactionController;
|
||||
late Animation<double> _reaction;
|
||||
|
||||
late bool isFocused;
|
||||
|
||||
bool get isInteractive => widget.onChanged != null;
|
||||
|
||||
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
|
||||
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _handleTap),
|
||||
};
|
||||
|
||||
// A non-null boolean value that changes to true at the end of a drag if the
|
||||
// switch must be animated to the position indicated by the widget's value.
|
||||
bool needsPositionAnimation = false;
|
||||
@ -188,6 +200,8 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
isFocused = false;
|
||||
|
||||
_tap = TapGestureRecognizer()
|
||||
..onTapDown = _handleTapDown
|
||||
..onTapUp = _handleTapUp
|
||||
@ -253,7 +267,7 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
|
||||
_reactionController.forward();
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
void _handleTap([Intent? _]) {
|
||||
if (isInteractive) {
|
||||
widget.onChanged!(!widget.value);
|
||||
_emitVibration();
|
||||
@ -322,9 +336,19 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
|
||||
}
|
||||
}
|
||||
|
||||
void _onShowFocusHighlight(bool showHighlight) {
|
||||
setState(() { isFocused = showHighlight; });
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final CupertinoThemeData theme = CupertinoTheme.of(context);
|
||||
final Color activeColor = CupertinoDynamicColor.resolve(
|
||||
widget.activeColor
|
||||
?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
|
||||
?? CupertinoColors.systemGreen,
|
||||
context,
|
||||
);
|
||||
if (needsPositionAnimation) {
|
||||
_resumePositionAnimation();
|
||||
}
|
||||
@ -332,19 +356,29 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
|
||||
cursor: isInteractive && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
|
||||
child: Opacity(
|
||||
opacity: widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0,
|
||||
child: _CupertinoSwitchRenderObjectWidget(
|
||||
value: widget.value,
|
||||
activeColor: CupertinoDynamicColor.resolve(
|
||||
widget.activeColor
|
||||
?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
|
||||
?? CupertinoColors.systemGreen,
|
||||
context,
|
||||
child: FocusableActionDetector(
|
||||
onShowFocusHighlight: _onShowFocusHighlight,
|
||||
actions: _actionMap,
|
||||
enabled: isInteractive,
|
||||
child: _CupertinoSwitchRenderObjectWidget(
|
||||
value: widget.value,
|
||||
activeColor: activeColor,
|
||||
trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
|
||||
thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context),
|
||||
// Opacity, lightness, and saturation values were aproximated with
|
||||
// color pickers on the switches in the macOS settings.
|
||||
focusColor: CupertinoDynamicColor.resolve(
|
||||
widget.focusColor ??
|
||||
HSLColor
|
||||
.fromColor(activeColor.withOpacity(0.80))
|
||||
.withLightness(0.69).withSaturation(0.835)
|
||||
.toColor(),
|
||||
context),
|
||||
onChanged: widget.onChanged,
|
||||
textDirection: Directionality.of(context),
|
||||
isFocused: isFocused,
|
||||
state: this,
|
||||
),
|
||||
trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
|
||||
thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context),
|
||||
onChanged: widget.onChanged,
|
||||
textDirection: Directionality.of(context),
|
||||
state: this,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -367,8 +401,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
required this.activeColor,
|
||||
required this.trackColor,
|
||||
required this.thumbColor,
|
||||
required this.focusColor,
|
||||
required this.onChanged,
|
||||
required this.textDirection,
|
||||
required this.isFocused,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@ -376,9 +412,11 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final Color activeColor;
|
||||
final Color trackColor;
|
||||
final Color thumbColor;
|
||||
final Color focusColor;
|
||||
final ValueChanged<bool>? onChanged;
|
||||
final _CupertinoSwitchState state;
|
||||
final TextDirection textDirection;
|
||||
final bool isFocused;
|
||||
|
||||
@override
|
||||
_RenderCupertinoSwitch createRenderObject(BuildContext context) {
|
||||
@ -387,8 +425,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
activeColor: activeColor,
|
||||
trackColor: trackColor,
|
||||
thumbColor: thumbColor,
|
||||
focusColor: focusColor,
|
||||
onChanged: onChanged,
|
||||
textDirection: textDirection,
|
||||
isFocused: isFocused,
|
||||
state: state,
|
||||
);
|
||||
}
|
||||
@ -401,8 +441,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..activeColor = activeColor
|
||||
..trackColor = trackColor
|
||||
..thumbColor = thumbColor
|
||||
..focusColor = focusColor
|
||||
..onChanged = onChanged
|
||||
..textDirection = textDirection;
|
||||
..textDirection = textDirection
|
||||
..isFocused = isFocused;
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,8 +468,10 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
|
||||
required Color activeColor,
|
||||
required Color trackColor,
|
||||
required Color thumbColor,
|
||||
required Color focusColor,
|
||||
ValueChanged<bool>? onChanged,
|
||||
required TextDirection textDirection,
|
||||
required bool isFocused,
|
||||
required _CupertinoSwitchState state,
|
||||
}) : assert(value != null),
|
||||
assert(activeColor != null),
|
||||
@ -435,9 +479,11 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
|
||||
_value = value,
|
||||
_activeColor = activeColor,
|
||||
_trackColor = trackColor,
|
||||
_focusColor = focusColor,
|
||||
_thumbPainter = CupertinoThumbPainter.switchThumb(color: thumbColor),
|
||||
_onChanged = onChanged,
|
||||
_textDirection = textDirection,
|
||||
_isFocused = isFocused,
|
||||
_state = state,
|
||||
super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) {
|
||||
state.position.addListener(markNeedsPaint);
|
||||
@ -490,6 +536,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Color get focusColor => _focusColor;
|
||||
Color _focusColor;
|
||||
set focusColor(Color value) {
|
||||
assert(value != null);
|
||||
if (value == _focusColor) {
|
||||
return;
|
||||
}
|
||||
_focusColor = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
ValueChanged<bool>? get onChanged => _onChanged;
|
||||
ValueChanged<bool>? _onChanged;
|
||||
set onChanged(ValueChanged<bool>? value) {
|
||||
@ -515,6 +572,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
bool get isFocused => _isFocused;
|
||||
bool _isFocused;
|
||||
set isFocused(bool value) {
|
||||
assert(value != null);
|
||||
if(value == _isFocused) {
|
||||
return;
|
||||
}
|
||||
_isFocused = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
bool get isInteractive => onChanged != null;
|
||||
|
||||
@override
|
||||
@ -570,6 +638,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
|
||||
final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius));
|
||||
canvas.drawRRect(trackRRect, paint);
|
||||
|
||||
if(_isFocused) {
|
||||
// Paints a border around the switch in the focus color.
|
||||
final RRect borderTrackRRect = trackRRect.inflate(1.75);
|
||||
|
||||
final Paint borderPaint = Paint()
|
||||
..color = focusColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.5;
|
||||
|
||||
canvas.drawRRect(borderTrackRRect, borderPaint);
|
||||
}
|
||||
|
||||
final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue;
|
||||
final double thumbLeft = lerpDouble(
|
||||
trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
|
||||
|
@ -48,6 +48,39 @@ void main() {
|
||||
expect(value, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('CupertinoSwitch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
|
||||
bool value = true;
|
||||
Widget buildApp({bool enabled = true}) {
|
||||
return CupertinoApp(
|
||||
home: CupertinoPageScaffold(
|
||||
child: Center(
|
||||
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
||||
return CupertinoSwitch(
|
||||
value: value,
|
||||
onChanged: enabled ? (bool newValue) {
|
||||
setState(() {
|
||||
value = newValue;
|
||||
});
|
||||
} : null,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await tester.pumpWidget(buildApp());
|
||||
await tester.pumpAndSettle();
|
||||
expect(value, isTrue);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
||||
await tester.pumpAndSettle();
|
||||
expect(value, isFalse);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
||||
await tester.pumpAndSettle();
|
||||
expect(value, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Switch emits light haptic vibration on tap', (WidgetTester tester) async {
|
||||
final Key switchKey = UniqueKey();
|
||||
bool value = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user