Improve tooltip behavior (#4284)
As requested by the material design team. Fixes #4182
This commit is contained in:
parent
787fb3be7d
commit
b8fb46e425
@ -5,6 +5,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
@ -12,7 +13,7 @@ import 'typography.dart';
|
|||||||
|
|
||||||
const double _kScreenEdgeMargin = 10.0;
|
const double _kScreenEdgeMargin = 10.0;
|
||||||
const Duration _kFadeDuration = const Duration(milliseconds: 200);
|
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.
|
/// A material design tooltip.
|
||||||
///
|
///
|
||||||
@ -105,21 +106,8 @@ class _TooltipState extends State<Tooltip> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleStatusChanged(AnimationStatus status) {
|
void _handleStatusChanged(AnimationStatus status) {
|
||||||
switch (status) {
|
if (status == AnimationStatus.dismissed)
|
||||||
case AnimationStatus.completed:
|
_removeEntry();
|
||||||
assert(_entry != null);
|
|
||||||
assert(_timer == null);
|
|
||||||
resetShowTimer();
|
|
||||||
break;
|
|
||||||
case AnimationStatus.dismissed:
|
|
||||||
assert(_entry != null);
|
|
||||||
assert(_timer == null);
|
|
||||||
_entry.remove();
|
|
||||||
_entry = null;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -134,61 +122,58 @@ class _TooltipState extends State<Tooltip> {
|
|||||||
_entry.markNeedsBuild();
|
_entry.markNeedsBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetShowTimer() {
|
void ensureTooltipVisible() {
|
||||||
assert(_controller.status == AnimationStatus.completed);
|
if (_entry != null)
|
||||||
assert(_entry != null);
|
return; // Already visible.
|
||||||
_timer = new Timer(_kShowDuration, hideTooltip);
|
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() {
|
void _removeEntry() {
|
||||||
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() {
|
|
||||||
assert(_entry != null);
|
assert(_entry != null);
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = null;
|
_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
|
@override
|
||||||
void deactivate() {
|
void deactivate() {
|
||||||
if (_entry != null)
|
if (_entry != null)
|
||||||
hideTooltip();
|
_controller.reverse();
|
||||||
super.deactivate();
|
super.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.stop();
|
if (_entry != null)
|
||||||
_entry?.remove();
|
_removeEntry();
|
||||||
_entry = null;
|
|
||||||
assert(_timer == null);
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +182,7 @@ class _TooltipState extends State<Tooltip> {
|
|||||||
assert(Overlay.of(context, debugRequiredFor: config) != null);
|
assert(Overlay.of(context, debugRequiredFor: config) != null);
|
||||||
return new GestureDetector(
|
return new GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onLongPress: showTooltip,
|
onLongPress: ensureTooltipVisible,
|
||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
child: new Semantics(
|
child: new Semantics(
|
||||||
label: config.message,
|
label: config.message,
|
||||||
|
@ -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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
|
|
||||||
/********************* 800x600 screen
|
/********************* 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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
|
|
||||||
/********************* 800x600 screen
|
/********************* 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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
|
|
||||||
/********************* 800x600 screen
|
/********************* 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)
|
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:
|
// 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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
|
|
||||||
/********************* 800x600 screen
|
/********************* 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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
|
|
||||||
/********************* 800x600 screen
|
/********************* 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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
|
|
||||||
/********************* 800x600 screen
|
/********************* 800x600 screen
|
||||||
@ -434,7 +434,7 @@ void main() {
|
|||||||
client.updates.clear();
|
client.updates.clear();
|
||||||
|
|
||||||
// before using "as dynamic" in your code, see note top of file
|
// 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)
|
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||||
expect(client.updates.length, equals(2));
|
expect(client.updates.length, equals(2));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user