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

Flutter WebView能否将加密地址转为可点击按钮并实现双向通信?

解决方案

1. 实现加密地址自动转为可点击元素

核心思路是通过JavaScript注入,在网页加载完成后遍历DOM,匹配加密地址并替换为可点击的交互元素,点击时触发与Flutter的通信。

  • 第一步:定义加密地址的正则表达式(以以太坊地址为例,可根据你的地址格式调整):
    const addressRegex = /0x[a-fA-F0-9]{40}/g;
    
  • 第二步:编写注入的JS脚本,替换页面中的地址文本为可点击标签,并绑定点击事件:
    function convertAddressesToClickable() {
      // 遍历所有文本节点
      function traverse(node) {
        if (node.nodeType === Node.TEXT_NODE) {
          const text = node.textContent;
          const matches = text.match(addressRegex);
          if (matches) {
            const fragment = document.createDocumentFragment();
            let lastIndex = 0;
            matches.forEach(match => {
              const index = text.indexOf(match, lastIndex);
              // 添加匹配前的文本
              fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)));
              // 创建可点击的span标签
              const span = document.createElement('span');
              span.textContent = match;
              span.style.color = '#0066cc';
              span.style.cursor = 'pointer';
              span.style.textDecoration = 'underline';
              // 点击时向Flutter发送地址数据
              span.addEventListener('click', () => {
                window.flutterChannel.postMessage(match);
              });
              fragment.appendChild(span);
              lastIndex = index + match.length;
            });
            // 添加剩余文本
            fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
            node.parentNode.replaceChild(fragment, node);
          }
        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'SCRIPT' && node.tagName !== 'STYLE') {
          // 跳过脚本和样式标签,遍历子节点
          Array.from(node.childNodes).forEach(traverse);
        }
      }
      traverse(document.body);
    }
    // 页面加载完成后执行
    window.addEventListener('load', convertAddressesToClickable);
    // 如果页面是动态加载内容,可监听DOM变化后重新执行
    const observer = new MutationObserver(convertAddressesToClickable);
    observer.observe(document.body, { childList: true, subtree: true });
    

2. WebView与Flutter双向通信

使用webview_flutter包的JavaScriptChannel实现双向通信:

从WebView向Flutter发送数据

在WebView配置中添加javascriptChannels,接收WebView发来的地址数据,然后在Flutter端弹出弹窗展示信息:

JavaScriptChannel(
  name: 'flutterChannel',
  onMessageReceived: (JavaScriptMessage message) {
    final address = message.message;
    // 弹出Flutter原生弹窗展示地址信息
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('地址详情'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('地址: $address'),
            // 这里可以添加更多地址相关信息,比如查询链上数据
            const SizedBox(height: 10),
            const Text('余额: 0.00 ETH'), // 示例数据
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('关闭'),
          ),
        ],
      ),
    );
  },
),

从Flutter向WebView发送数据

通过WebViewController执行JS脚本,向WebView传递数据:

// 示例:向WebView发送指令,高亮某个地址
void highlightAddress(String address) {
  _webViewController?.runJavascript(
    """
    const elements = document.querySelectorAll('span');
    elements.forEach(el => {
      if (el.textContent === '$address') {
        el.style.backgroundColor = '#ffff00';
      }
    });
    """,
  );
}

3. 完整Flutter代码示例

首先在pubspec.yaml中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^4.4.0 # 使用最新稳定版

主页面代码:

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class CryptoWebView extends StatefulWidget {
  final String url;
  const CryptoWebView({super.key, required this.url});

  @override
  State<CryptoWebView> createState() => _CryptoWebViewState();
}

class _CryptoWebViewState extends State<CryptoWebView> {
  WebViewController? _webViewController;

  // 注入的JS脚本
  final String jsScript = """
    const addressRegex = /0x[a-fA-F0-9]{40}/g;
    function convertAddressesToClickable() {
      function traverse(node) {
        if (node.nodeType === Node.TEXT_NODE) {
          const text = node.textContent;
          const matches = text.match(addressRegex);
          if (matches) {
            const fragment = document.createDocumentFragment();
            let lastIndex = 0;
            matches.forEach(match => {
              const index = text.indexOf(match, lastIndex);
              fragment.appendChild(document.createTextNode(text.slice(lastIndex, index)));
              const span = document.createElement('span');
              span.textContent = match;
              span.style.color = '#0066cc';
              span.style.cursor = 'pointer';
              span.style.textDecoration = 'underline';
              span.addEventListener('click', () => {
                window.flutterChannel.postMessage(match);
              });
              fragment.appendChild(span);
              lastIndex = index + match.length;
            });
            fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
            node.parentNode.replaceChild(fragment, node);
          }
        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'SCRIPT' && node.tagName !== 'STYLE') {
          Array.from(node.childNodes).forEach(traverse);
        }
      }
      traverse(document.body);
    }
    window.addEventListener('load', convertAddressesToClickable);
    const observer = new MutationObserver(convertAddressesToClickable);
    observer.observe(document.body, { childList: true, subtree: true });
  """;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('加密地址查看器')),
      body: WebView(
        initialUrl: widget.url,
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (controller) {
          _webViewController = controller;
          // 注入JS脚本
          controller.runJavascript(jsScript);
        },
        javascriptChannels: {
          JavaScriptChannel(
            name: 'flutterChannel',
            onMessageReceived: (message) {
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text('地址详情'),
                  content: Column(
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('地址: ${message.message}'),
                      const SizedBox(height: 10),
                      const Text('示例余额: 0.00 ETH'),
                      const Text('示例交易数: 0'),
                    ],
                  ),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('关闭'),
                    ),
                  ],
                ),
              );
            },
          ),
        },
      ),
    );
  }
}

// 使用示例
void main() => runApp(const MaterialApp(home: CryptoWebView(url: 'https://example.com')));

关键说明

  • 修改WebView的UI完全可行:通过JS注入可以修改网页的DOM结构、样式,也可以结合Flutter原生组件实现更丰富的交互(比如用Flutter弹窗代替网页弹窗)。
  • 动态内容适配:代码中添加了MutationObserver,可以监听网页DOM变化,自动处理动态加载的加密地址。
  • 正则调整:根据你实际的加密地址格式修改addressRegex,比如比特币地址的正则是[13][a-km-zA-HJ-NP-Z1-9]{25,34}。

内容的提问来源于stack exchange,提问作者Sandeep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:20:38