You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:如何在WebViewWidget中选中指定文本并调用TTS播放音频

Flutter:如何在WebViewWidget中选中指定文本并调用TTS播放音频

我完全理解你的需求——让用户能在WebView里选中专属样式(下划线/高亮)的文本,然后触发TTS朗读。你目前尝试了用GestureDetector搭配WebView的手势识别,但遇到了触发不顺畅的问题,同时也在考虑用JavaScript来实现,我来分享两种可行的解决方案,帮你搞定这个功能:


方案一:优化Dart侧手势识别+JS获取选中文本

WebView本身会消费大部分手势事件,所以外层的GestureDetector经常会“抢不到”事件。我们可以调整WebView的手势识别配置,结合JS来获取并校验选中的文本:

  1. 调整WebView的手势识别器:
    优先用LongPressGestureRecognizer来触发文本选择(这和Web端默认的选文本逻辑一致,用户体验更自然),然后在长按结束后通过JS获取选中的文本,并检查它是否符合你的样式要求。

    修改你的WebView代码如下:

    child: WebViewWidget(
      controller: _controller,
      gestureRecognizers: {
        Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()
          ..onLongPressEnd = (LongPressEndDetails details) async {
            // 执行JS获取选中的文本,同时校验是否是下划线/高亮文本
            String result = await _controller.runJavascriptReturningResult('''
              const selection = window.getSelection();
              const selectedText = selection.toString().trim();
              if (!selectedText) return '';
              // 检查选中文本所在元素是否有下划线样式
              const selectedElement = selection.anchorNode.parentElement;
              const isUnderlined = selectedElement.tagName === 'U' || 
                                    selectedElement.style.textDecoration.includes('underline') ||
                                    selectedElement.classList.contains('highlighted-text'); // 替换成你的高亮类名
              isUnderlined ? selectedText : '';
            ''');
            // 去除JS返回的引号,拿到纯文本
            String selectedText = result.replaceAll('"', '');
            if (selectedText.isNotEmpty) {
              TTSHelper.speak(text: selectedText);
            }
          }),
      },
    ),
    
  2. 注意事项:

    • 这段JS会检查选中文本的父元素是否是<u>标签、带有下划线样式,或者属于自定义的高亮类,你可以根据实际HTML结构调整判断逻辑。
    • 记得确保WebViewController已经初始化完成,避免调用runJavascriptReturningResult时出现空指针。

方案二:JS绑定元素事件+Flutter通信(更稳定推荐)

如果Dart侧的手势冲突不好解决,直接在HTML里用JS给目标文本绑定事件,再通过JavaScriptChannel把文本传给Flutter触发TTS,这个方案更直接可靠:

  1. 在Flutter中创建JavaScriptChannel:
    先给WebViewController添加一个通信通道,用来接收JS传来的文本:

    late final WebViewController _controller;
    
    @override
    void initState() {
      super.initState();
      _controller = WebViewController()
        ..addJavaScriptChannel(
          'TTSChannel',
          onMessageReceived: (JavaScriptMessage message) {
            String textToSpeak = message.message;
            if (textToSpeak.isNotEmpty) {
              TTSHelper.speak(text: textToSpeak);
            }
          },
        )
        ..loadHtmlString(_prepareHTML(content));
    }
    
  2. 修改_prepareHTML函数,添加JS代码:
    在你的HTML模板里加入一段JS,自动给所有下划线/高亮文本绑定点击(或长按)事件,点击时把文本传给Flutter:

    String _prepareHTML(String content) {
      return """<!DOCTYPE html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
    body {background-color: ${getCSSColor(context, CustomColors.mainAppBG)};}
    h1, h2, h3, h4, h5, h6 {color: ${getCSSColor(context, CustomColors.blueWhiteText)};}
    p {color: ${getCSSColor(context, CustomColors.text)};}
    table {
    width: 100%;
    table-layout: fixed;
    word-wrap: break-word;
    }
    table, tr, th, td {
    color: ${getCSSColor(context, CustomColors.text)};
    border: 1px solid ${getCSSColor(context, CustomColors.text)};
    border-collapse: collapse;
    }
    th, td {
    padding: 6px 12px;
    font-weight: bold;
    font-size: 1.25em;
    }
    /* 给可点击的文本加视觉提示 */
    u, .highlighted-text {
      cursor: pointer;
      transition: opacity 0.2s;
    }
    u:hover, .highlighted-text:hover {
      opacity: 0.8;
    }
    </style>
    <script>
    // 页面加载完成后绑定事件
    window.addEventListener('load', () => {
      // 选择所有目标元素:<u>标签和自定义高亮类的元素
      const targetTexts = document.querySelectorAll('u, .highlighted-text');
      targetTexts.forEach(element => {
        // 绑定点击事件
        element.addEventListener('click', () => {
          const text = element.textContent.trim();
          if (text) {
            // 发送文本到Flutter侧
            TTSChannel.postMessage(text);
          }
        });
        // 可选:绑定长按事件,和点击二选一即可
        element.addEventListener('contextmenu', (e) => {
          e.preventDefault(); // 阻止默认右键菜单
          const text = element.textContent.trim();
          if (text) {
            TTSChannel.postMessage(text);
          }
        });
      });
    });
    </script>
    </head>
    <body>
    $content
    </body>
    </html>""";
    }
    
  3. 方案优势:

    • 完全避开Flutter和WebView的手势冲突问题,直接在Web层面处理交互。
    • 可以给目标文本添加 hover 效果,让用户更清楚哪些文本是可交互的。
    • 支持点击或长按两种触发方式,你可以根据需求选择。

两种方案都能实现你的需求,我个人更推荐第二种JS+通信的方案,因为它更稳定,也更容易扩展(比如后续要加其他交互)。

备注:内容来源于stack exchange,提问作者Cedric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 14:02:51