当我们上线一个新功能时经常需要做个气泡引导用户使用,但如何在不改变布局的情况下给Widget加上气泡呢? 我们可以通过OverlayEntry在更高的页面层级上插入气泡,同时根据相对的Widget位置来计算气泡的位置,具体代码实现如下。 需要注意的是,虽然我们在Widget的dispose中会销毁气泡,但如果Widget是页面并且页面有弹窗,气泡会出现在弹窗上,所以使用的时候需要在弹窗前主动销毁气泡。
// 堆代码 duidaima.com
// 以相对widget为中心扩展刚好容纳bubble大小的Rect
Rect _expandRelativeRect(BuildContext relativeContext, Size bubbleSize, bool allowOutsideScreen) {
if ((relativeContext as Element)?.isElementActive != true) {
return Rect.zero;
}
// 找到相对widget的位置和大小
final RenderBox renderBox = relativeContext.findRenderObject();
final Offset offset = renderBox.localToGlobal(Offset.zero);
final Size size = renderBox.size;
// 以相对widget为中心扩展刚好容纳bubble大小的Rect
final rect = Rect.fromLTWH(offset.dx - bubbleSize.width, offset.dy - bubbleSize.height,
bubbleSize.width * 2 + size.width, bubbleSize.height * 2 + size.height);
if (allowOutsideScreen) {
return rect;
} else {
final screenSize = MediaQueryData.fromWindow(ui.window).size;
final screenRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
return rect.intersect(screenRect);
}
}
/// 构建气泡Entry
OverlayEntry bubbleEntry = OverlayEntry(builder: (bubbleContext) {
Rect rect = _expandRelativeRect(relativeContext, bubbleSize, allowOutsideScreen);
return Positioned.fromRect(
rect: rect,
child: Align(
alignment: alignment,
child: SizedBox(
child: bubble.call(bubbleContext),
width: bubbleSize.width,
height: bubbleSize.height,
)));
});
Overlay.of(context).insert(bubbleEntry);
/// 关闭气泡
VoidCallback hideBubbleCallback = () {
if (!bubbleEntry.mounted) {
return;
}
bubbleEntry.remove();
};