mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Enable InkWell to sync its hovered state when its enabled or disabled (#62913)
This commit is contained in:
parent
e86caf7a08
commit
4500a451dc
@ -773,7 +773,11 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
void didUpdateWidget(_InkResponseStateWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
|
||||
_handleHoverChange(_hovering);
|
||||
if (enabled) {
|
||||
// Don't call wigdet.onHover because many wigets, including the button
|
||||
// widgets, apply setState to an ancestor context from onHover.
|
||||
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
|
||||
}
|
||||
_updateFocusHighlights();
|
||||
}
|
||||
}
|
||||
@ -818,7 +822,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
return null;
|
||||
}
|
||||
|
||||
void updateHighlight(_HighlightType type, {@required bool value}) {
|
||||
void updateHighlight(_HighlightType type, { @required bool value, bool callOnHover = true }) {
|
||||
final InkHighlight highlight = _highlights[type];
|
||||
void handleInkRemoval() {
|
||||
assert(_highlights[type] != null);
|
||||
@ -862,7 +866,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
widget.onHighlightChanged(value);
|
||||
break;
|
||||
case _HighlightType.hover:
|
||||
if (widget.onHover != null)
|
||||
if (callOnHover && widget.onHover != null)
|
||||
widget.onHover(value);
|
||||
break;
|
||||
case _HighlightType.focus:
|
||||
@ -1040,15 +1044,24 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
|
||||
bool get enabled => _isWidgetEnabled(widget);
|
||||
|
||||
void _handleMouseEnter(PointerEnterEvent event) => _handleHoverChange(true);
|
||||
void _handleMouseExit(PointerExitEvent event) => _handleHoverChange(false);
|
||||
void _handleHoverChange(bool hovering) {
|
||||
if (_hovering != hovering) {
|
||||
_hovering = hovering;
|
||||
updateHighlight(_HighlightType.hover, value: enabled && _hovering);
|
||||
void _handleMouseEnter(PointerEnterEvent event) {
|
||||
_hovering = true;
|
||||
if (enabled) {
|
||||
_handleHoverChange();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleMouseExit(PointerExitEvent event) {
|
||||
_hovering = false;
|
||||
// If the exit occurs after we've been disabled, we still
|
||||
// want to take down the highlights and run widget.onHover.
|
||||
_handleHoverChange();
|
||||
}
|
||||
|
||||
void _handleHoverChange() {
|
||||
updateHighlight(_HighlightType.hover, value: _hovering);
|
||||
}
|
||||
|
||||
bool get _canRequestFocus {
|
||||
final NavigationMode mode = MediaQuery.of(context, nullOk: true)?.navigationMode ?? NavigationMode.traditional;
|
||||
switch (mode) {
|
||||
@ -1076,7 +1089,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
|
||||
<MaterialState>{
|
||||
if (!enabled) MaterialState.disabled,
|
||||
if (_hovering) MaterialState.hovered,
|
||||
if (_hovering && enabled) MaterialState.hovered,
|
||||
if (_hasFocus) MaterialState.focused,
|
||||
},
|
||||
);
|
||||
@ -1091,8 +1104,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
autofocus: widget.autofocus,
|
||||
child: MouseRegion(
|
||||
cursor: effectiveMouseCursor,
|
||||
onEnter: enabled ? _handleMouseEnter : null,
|
||||
onExit: enabled ? _handleMouseExit : null,
|
||||
onEnter: _handleMouseEnter,
|
||||
onExit: _handleMouseExit,
|
||||
child: GestureDetector(
|
||||
onTapDown: enabled ? _handleTapDown : null,
|
||||
onTap: enabled ? () => _handleTap(context) : null,
|
||||
|
||||
@ -1191,4 +1191,103 @@ void main() {
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
|
||||
});
|
||||
|
||||
testWidgets('disabled and hovered inkwell responds to mouse-exit', (WidgetTester tester) async {
|
||||
int onHoverCount = 0;
|
||||
bool hover;
|
||||
|
||||
Widget buildFrame({ bool enabled }) {
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: InkWell(
|
||||
onTap: enabled ? () { } : null,
|
||||
onHover: (bool value) {
|
||||
onHoverCount += 1;
|
||||
hover = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(InkWell)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(onHoverCount, 1);
|
||||
expect(hover, true);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: false));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(Offset.zero);
|
||||
// Even though the InkWell has been disabled, the mouse-exit still
|
||||
// causes onHover(false) to be called.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(InkWell)));
|
||||
await tester.pumpAndSettle();
|
||||
// We no longer see hover events because the InkWell is disabled
|
||||
// and it's no longer in the "hovering" state.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
await tester.pumpAndSettle();
|
||||
// The InkWell was enabled while it contained the mouse, however
|
||||
// we do not call onHover() because it may call setState().
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(InkWell)) - const Offset(1, 1));
|
||||
await tester.pumpAndSettle();
|
||||
// Moving the mouse a little within the InkWell doesn't change anything.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
});
|
||||
|
||||
testWidgets('Changing InkWell.enabled should not trigger TextButton setState()', (WidgetTester tester) async {
|
||||
Widget buildFrame({ bool enabled }) {
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: TextButton(
|
||||
onPressed: enabled ? () { } : null,
|
||||
child: const Text('button'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: false));
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Rebuilding the button with enabled:true causes InkWell.didUpdateWidget()
|
||||
// to be called per the change in its enabled flag. If onHover() was called,
|
||||
// this test would crash.
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Rebuild again, with enabled:false
|
||||
await gesture.moveBy(const Offset(1, 1));
|
||||
await tester.pumpWidget(buildFrame(enabled: false));
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user