diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index bbf97deaaf..cd732cbc87 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:math' as math; +import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; @@ -12,7 +13,7 @@ import 'typography.dart'; const double _kScreenEdgeMargin = 10.0; const Duration _kFadeDuration = const Duration(milliseconds: 200); -const Duration _kShowDuration = const Duration(seconds: 2); +const Duration _kShowDuration = const Duration(milliseconds: 1500); /// A material design tooltip. /// @@ -105,21 +106,8 @@ class _TooltipState extends State { } void _handleStatusChanged(AnimationStatus status) { - switch (status) { - case AnimationStatus.completed: - assert(_entry != null); - assert(_timer == null); - resetShowTimer(); - break; - case AnimationStatus.dismissed: - assert(_entry != null); - assert(_timer == null); - _entry.remove(); - _entry = null; - break; - default: - break; - } + if (status == AnimationStatus.dismissed) + _removeEntry(); } @override @@ -134,61 +122,58 @@ class _TooltipState extends State { _entry.markNeedsBuild(); } - void resetShowTimer() { - assert(_controller.status == AnimationStatus.completed); - assert(_entry != null); - _timer = new Timer(_kShowDuration, hideTooltip); + void ensureTooltipVisible() { + if (_entry != null) + return; // Already visible. + RenderBox box = context.findRenderObject(); + Point target = box.localToGlobal(box.size.center(Point.origin)); + _entry = new OverlayEntry(builder: (BuildContext context) { + return new _TooltipOverlay( + message: config.message, + height: config.height, + padding: config.padding, + animation: new CurvedAnimation( + parent: _controller, + curve: Curves.ease + ), + target: target, + verticalOffset: config.verticalOffset, + preferBelow: config.preferBelow + ); + }); + Overlay.of(context, debugRequiredFor: config).insert(_entry); + GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent); + _controller.forward(); } - void showTooltip() { - if (_entry == null) { - RenderBox box = context.findRenderObject(); - Point target = box.localToGlobal(box.size.center(Point.origin)); - _entry = new OverlayEntry(builder: (BuildContext context) { - return new _TooltipOverlay( - message: config.message, - height: config.height, - padding: config.padding, - animation: new CurvedAnimation( - parent: _controller, - curve: Curves.ease - ), - target: target, - verticalOffset: config.verticalOffset, - preferBelow: config.preferBelow - ); - }); - Overlay.of(context, debugRequiredFor: config).insert(_entry); - } - _timer?.cancel(); - if (_controller.status != AnimationStatus.completed) { - _timer = null; - _controller.forward(); - } else { - resetShowTimer(); - } - } - - void hideTooltip() { + void _removeEntry() { assert(_entry != null); _timer?.cancel(); _timer = null; - _controller.reverse(); + _entry.remove(); + _entry = null; + GestureBinding.instance.pointerRouter.removeGlobalRoute(_handlePointerEvent); + } + + void _handlePointerEvent(PointerEvent event) { + assert(_entry != null); + if (event is PointerUpEvent || event is PointerCancelEvent) + _timer = new Timer(_kShowDuration, _controller.reverse); + else if (event is PointerDownEvent) + _controller.reverse(); } @override void deactivate() { if (_entry != null) - hideTooltip(); + _controller.reverse(); super.deactivate(); } @override void dispose() { - _controller.stop(); - _entry?.remove(); - _entry = null; - assert(_timer == null); + if (_entry != null) + _removeEntry(); super.dispose(); } @@ -197,7 +182,7 @@ class _TooltipState extends State { assert(Overlay.of(context, debugRequiredFor: config) != null); return new GestureDetector( behavior: HitTestBehavior.opaque, - onLongPress: showTooltip, + onLongPress: ensureTooltipVisible, excludeFromSemantics: true, child: new Semantics( label: config.message, diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index a7a5508c3d..484143a31a 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -57,7 +57,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen @@ -105,7 +105,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen @@ -154,7 +154,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen @@ -205,7 +205,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) // we try to put it here but it doesn't fit: @@ -267,7 +267,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen @@ -317,7 +317,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen @@ -369,7 +369,7 @@ void main() { ] ) ); - (key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file + (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) /********************* 800x600 screen @@ -434,7 +434,7 @@ void main() { client.updates.clear(); // before using "as dynamic" in your code, see note top of file - (key.currentState as dynamic).showTooltip(); // this triggers a rebuild of the semantics because the tree changes + (key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) expect(client.updates.length, equals(2));