当鼠标悬停在特定的 TextSpan
上时如何显示工具提示?我找到了 Flutter: How to display Tooltip for TextSpan inside RichText,但这是在点击 TextSpan
时,而不是用鼠标悬停,它使用 SnackBar
,而我想要一个工具提示。
不幸的是,我不能只将 TextSpan
包装在 ToolTip
中,因为我的用例是在 TextEditingController.buildTextSpan
的覆盖中返回 TextSpan(children: childSpans)
,这意味着我必须使用 TextSpan
的子类。
答案 0 :(得分:1)
我没有找到直接的方法来做到这一点。
但这里有一个可能的解决方案,在整个 RichText
上使用 Hover,然后确定哪个 TextSpan
是目标,以及它是否有工具提示。
不过,这并不容易。系好安全带!
我试图让我的申请尽可能简单:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TextSpan Hover Demo',
home: HomePage(),
),
);
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _textKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: RichTextTooltipDetector(
textKey: _textKey,
child: RichText(
key: _textKey,
text: TextSpan(
text: 'Hello ',
children: <TextSpan>[
TextSpan(
text: 'bold',
style: TextStyle(fontWeight: FontWeight.bold),
semanticsLabel: 'Tooltip: Yeah! It works',
),
TextSpan(text: ' world!'),
],
),
),
),
),
);
}
}
这个 RichTextTooltipDetector
是我将工具提示作为 OverlayEntries
处理的地方。
我的 RichTextTooltipDetector
只是一个 MouseRegion
,我将在上面处理悬停事件。这个事件给了我鼠标的本地位置。这个位置与 RichText GlobalKey 一起,是我们确定是否有工具提示要显示的全部内容:
RichText
全局密钥,我得到 RenderParagraph
RenderParagraph
和 localPosition
的 PointerHoverEvent
,我得到 `InlineSpan,如果有的话TextSpan
时,我劫持了 semanticsLabel
。这让我很容易知道是否需要显示工具提示。剩下的只是基本的 OverlayEntry 管理:
OverlayEntry
、BuildContext
和工具提示文本创建 offset
Overlay.of(context).insert(_tooltipOverlay);
显示 OverlayEntryOverlayEntry
隐藏 _tooltipOverlay?.remove();
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class _RichTextTooltipDetectorState extends State<RichTextTooltipDetector> {
OverlayEntry _tooltipOverlay;
Timer _timer;
RenderParagraph get _renderParagraph =>
widget.textKey.currentContext?.findRenderObject() as RenderParagraph;
InlineSpan _span(PointerHoverEvent event, RenderParagraph paragraph) {
final textPosition = paragraph.getPositionForOffset(event.localPosition);
return paragraph.text.getSpanForPosition(textPosition);
}
String _tooltipText(PointerHoverEvent event, RenderParagraph paragraph) {
final span = _span(event, paragraph);
return span is TextSpan &&
span.semanticsLabel != null &&
span.semanticsLabel.startsWith('Tooltip: ')
? span.semanticsLabel.split('Tooltip: ')[1]
: '';
}
OverlayEntry _createTooltip(
BuildContext context, String text, Offset offset) {
return OverlayEntry(
builder: (context) => Positioned(
left: offset.dx,
top: offset.dy,
child: Material(
elevation: 4.0,
child: Container(
decoration: BoxDecoration(
color: Colors.white70,
border: Border.all(color: Colors.black87, width: 3.0),
),
padding: EdgeInsets.all(8.0),
child: Text(text),
),
),
),
);
}
void _showTooltip() {
Overlay.of(context).insert(_tooltipOverlay);
_timer = Timer(Duration(seconds: 1), () => _hideTooltip());
}
void _hideTooltip() {
_timer?.cancel();
_tooltipOverlay?.remove();
_tooltipOverlay = null;
_timer = null;
}
void _handleHover(BuildContext context, PointerHoverEvent event) {
_hideTooltip();
final paragraph = _renderParagraph;
if (event is! PointerHoverEvent || paragraph == null) return;
final tooltipText = _tooltipText(event, paragraph);
if (tooltipText.isNotEmpty) {
RenderBox renderBox = context.findRenderObject();
var offset = renderBox.localToGlobal(event.localPosition);
_tooltipOverlay = _createTooltip(context, tooltipText, offset);
_showTooltip();
}
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onHover: (event) => _handleHover(context, event),
child: widget.child,
);
}
}