我想获取用户长按
MarkdownBody
的flutter_markdown
位置的元素,以便弹出菜单供用户操作。我的问题是如何检索已长按的元素。
这应该可以通过将
builders
注册到您的 MarkdownWidget
来实现。
为了给您一个最小的例子,我将做出一些假设:
为此,我将创建一个自定义元素,我们称之为
custom-element
。该元素有一个属性 id
,它允许我们基于它显示不同的菜单:
[custom-element id='some-id']A custom element[/custom-element]
为了支持此自定义元素,我们创建了一个
CustomElementSyntax
和一个 CustomElementBuilder
。 CustomElementBuilder
负责为自定义元素创建 Widget。为了简单起见,我创建了一个 Text
小部件。然而,它必须被包裹在 GestureDetector
中才能处理长按。我们用 Builder
包裹所有内容,因为我们需要元素的 BuildContext
才能获得 RenderBox
来帮助我们确定菜单的位置。
完整代码如下:
https://dartpad.dev/?id=4e52a8a9467ed12d7cc459965e77a592
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
void main() {
runApp(
MaterialApp(
home: DemoPage(),
)
);
}
class DemoPage extends StatelessWidget {
const DemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: MarkdownBody(
data:
'This is a markdown widget with custom elements.\n\n[custom-element id="element-1"]Element 1[/custom-element]\n\n[custom-element id="element-2"]Element 2[/custom-element]',
builders: <String, MarkdownElementBuilder>{
'custom-element':
CustomElementBuilder(onLongPress: showMenuForCustomElement),
},
extensionSet: md.ExtensionSet(
[],
[CustomElementSyntax()],
),
),
),
),
);
}
Future<void> showMenuForCustomElement(
BuildContext context, String id) async {
final RenderBox? renderBox = context.findRenderObject() as RenderBox?;
if (renderBox == null) {
return;
}
final Offset offset = renderBox.localToGlobal(Offset.zero);
// You can use renderBox.size to set a position offset to the menu
final RelativeRect position = RelativeRect.fromLTRB(
offset.dx,
offset.dy + renderBox.size.height,
offset.dx,
offset.dy + renderBox.size.height,
);
print('Id: $id, position: $position');
final List<PopupMenuEntry<String>> items = switch (id) {
'element-1' => const [
PopupMenuItem(
value: 'action-1.1',
child: Text('Action 1.1'),
),
PopupMenuItem(
value: 'action-1.2',
child: Text('Action 1.2'),
),
],
'element-2' => const [
PopupMenuItem(
value: 'action-2.1',
child: Text('Action 2.1'),
),
PopupMenuItem(
value: 'action-2.2',
child: Text('Action 2.2'),
),
PopupMenuItem(
value: 'action-2.3',
child: Text('Action 2.3'),
),
],
_ => [],
};
if (items.isEmpty) {
return;
}
final selectedItem = await showMenu(
context: context,
position: position,
items: items,
);
print('Selected item: $selectedItem');
}
}
class CustomElementSyntax extends md.InlineSyntax {
static const _pattern =
r'^\[custom-element id="(.+)"\](.+)\[/custom-element\]$';
CustomElementSyntax() : super(_pattern);
@override
bool onMatch(md.InlineParser parser, Match match) {
final id = match[1]!;
final text = match[2]!;
final node = md.Element.text('custom-element', text);
node.attributes['id'] = id;
parser.addNode(node);
return true;
}
}
class CustomElementBuilder extends MarkdownElementBuilder {
final void Function(BuildContext context, String id)? onLongPress;
CustomElementBuilder({
this.onLongPress,
});
@override
Widget visitElementAfter(md.Element element, TextStyle? preferredStyle) {
final String textContent = element.textContent;
final String id = element.attributes['id'] ?? '';
return Builder(
builder: (context) {
return GestureDetector(
onLongPress: () => onLongPress?.call(context, id),
child: Text(textContent),
);
},
);
}
}