如何在 flutter_markdown 小部件上长按时获取元素?

问题描述 投票:0回答:1

我想获取用户长按

MarkdownBody
flutter_markdown
位置的元素,以便弹出菜单供用户操作。我的问题是如何检索已长按的元素。

flutter
1个回答
0
投票

这应该可以通过将

builders
注册到您的
MarkdownWidget
来实现。

为了给您一个最小的例子,我将做出一些假设:

  • 您可以控制 Markdown 数据
  • 您只想显示特定元素的菜单
  • 目标元素是内联的,看起来像一些链接
  • 每个元素可以有不同的菜单项

为此,我将创建一个自定义元素,我们称之为

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),
        );
      },
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.