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
相关产品推荐
相关产品推荐

