Flutter WebView 第三方库 内嵌 H5 页面的鸿蒙化适配与实战指南
Flutter WebView内嵌H5页面开发指南 本文介绍了使用Flutter的webview_flutter插件实现H5页面内嵌的技术方案。主要内容包括: WebView的应用场景:适用于隐私政策、活动页面、H5游戏和新闻内容等需要频繁更新或快速上线的页面 技术选型:推荐使用官方维护的webview_flutter插件,提供了完善的WebView功能支持 实现细节: 基础WebView页面构建
Flutter WebView 内嵌 H5 页面的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
各位小伙伴们好呀!👋 我是那个上海某高校的大一计算机学生,继续来给大家分享 Flutter for OpenHarmony 开发的学习心得!
今天要聊的是 WebView 内嵌 H5 页面!很多 App 都会内嵌一些 Web 页面,比如:
- 📜 隐私政策、用户协议
- 🎮 H5 游戏、活动页面
- 📰 新闻资讯
用 Flutter 原生写这些页面太麻烦了,直接用 WebView 嵌入现成的网页不香吗?今天就来详细分享一下!
一、功能引入介绍 📱
1.1 什么时候用 WebView?
- 📄 隐私政策、用户协议(更新频繁,适合 H5)
- 🎯 活动页面、促销专题(营销需求,快速上线)
- 🎮 H5 小游戏(无需原生开发)
- 📰 新闻详情、内容页(内容管理系统产出)
1.2 Flutter WebView 方案
| 插件 | 优点 | 缺点 |
|---|---|---|
| webview_flutter | 官方维护、功能完善 ✅ | 配置稍复杂 |
| flutter_webview_plugin | API 简单 | 已停止维护 |
我们选择 webview_flutter!
二、环境与依赖配置 🔧
2.1 pubspec.yaml 依赖
dependencies:
flutter:
sdk: flutter
# ========== WebView ==========
webview_flutter: ^4.10.0
三、分步实现完整代码 🚀
3.1 基础 WebView 页面
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
/// WebView 页面
///
/// 用于内嵌 H5 页面,支持加载进度显示、导航控制等
class WebViewPage extends StatefulWidget {
/// 网页 URL
final String url;
/// 页面标题(可选)
final String? title;
/// 是否显示 AppBar
final bool showAppBar;
const WebViewPage({
super.key,
required this.url,
this.title,
this.showAppBar = true,
});
State<WebViewPage> createState() => _WebViewPageState();
}
class _WebViewPageState extends State<WebViewPage> {
/// WebView 控制器
late final WebViewController _controller;
/// 是否正在加载
bool _isLoading = true;
/// 加载进度(0.0 - 1.0)
double _loadingProgress = 0;
void initState() {
super.initState();
_initWebView();
}
/// 初始化 WebView
void _initWebView() {
_controller = WebViewController()
// 允许 JavaScript
..setJavaScriptMode(JavaScriptMode.unrestricted)
// 设置背景色
..setBackgroundColor(Colors.white)
// 设置导航代理
..setNavigationDelegate(
NavigationDelegate(
// 加载进度回调
onProgress: (int progress) {
setState(() {
_loadingProgress = progress / 100;
});
},
// 页面开始加载
onPageStarted: (String url) {
setState(() => _isLoading = true);
},
// 页面加载完成
onPageFinished: (String url) {
setState(() => _isLoading = false);
},
// 加载错误
onWebResourceError: (WebResourceError error) {
debugPrint('WebView Error: ${error.description}');
},
// 导航请求拦截
onNavigationRequest: (NavigationRequest request) {
// 只允许 http/https 链接
if (request.url.startsWith('https://') ||
request.url.startsWith('http://')) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
),
)
// 加载网页
..loadRequest(Uri.parse(widget.url));
}
/// 处理返回按钮
Future<bool> _onWillPop() async {
// 如果可以返回上一页
if (await _controller.canGoBack()) {
_controller.goBack();
return false;
}
return true;
}
Widget build(BuildContext context) {
return PopScope(
canPop: false, // 禁用默认返回行为
onPopInvokedWithResult: (bool didPop, dynamic result) async {
if (didPop) return;
final shouldPop = await _onWillPop();
if (shouldPop && context.mounted) {
Navigator.of(context).pop();
}
},
child: Scaffold(
appBar: widget.showAppBar ? _buildAppBar() : null,
body: Column(
children: [
// 加载进度条
if (_isLoading)
LinearProgressIndicator(
value: _loadingProgress,
backgroundColor: Colors.grey[200],
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF6366F1),
),
),
// WebView 主体
Expanded(
child: WebViewWidget(controller: _controller),
),
],
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Colors.white,
elevation: 1,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1E293B)),
onPressed: () async {
if (await _controller.canGoBack()) {
_controller.goBack();
} else {
if (context.mounted) Navigator.pop(context);
}
},
),
title: Text(
widget.title ?? '加载中...',
style: const TextStyle(
color: Color(0xFF1E293B),
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
actions: [
IconButton(
icon: const Icon(Icons.refresh, color: Color(0xFF1E293B)),
onPressed: () => _controller.reload(),
),
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Color(0xFF1E293B)),
onSelected: _handleMenuAction,
itemBuilder: (context) => [
const PopupMenuItem(value: 'share', child: Text('分享')),
const PopupMenuItem(value: 'open_browser', child: Text('浏览器打开')),
const PopupMenuItem(value: 'copy_link', child: Text('复制链接')),
],
),
],
);
}
/// 处理菜单操作
void _handleMenuAction(String action) {
switch (action) {
case 'share':
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('分享功能开发中...')),
);
break;
case 'open_browser':
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('浏览器打开功能开发中...')),
);
break;
case 'copy_link':
// 复制链接到剪贴板
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('链接已复制')),
);
break;
}
}
}
3.2 WebView 页面工厂
为了方便复用,创建一些常用的 WebView 页面:
/// WebView 页面工厂
///
/// 提供常用的 WebView 页面配置
class WebViewPages {
// 常用页面 URL
static const String privacyPolicy = 'https://example.com/privacy';
static const String userAgreement = 'https://example.com/terms';
static const String helpCenter = 'https://example.com/help';
static const String promotionActivity = 'https://example.com/promotion';
/// 构建 WebView 页面
static Widget buildPage(String url, {String? title}) {
return WebViewPage(url: url, title: title);
}
/// 隐私政策页面
static Widget privacyPolicyPage() {
return const WebViewPage(
url: privacyPolicy,
title: '隐私政策',
);
}
/// 用户协议页面
static Widget userAgreementPage() {
return const WebViewPage(
url: userAgreement,
title: '用户协议',
);
}
/// 帮助中心页面
static Widget helpCenterPage() {
return const WebViewPage(
url: helpCenter,
title: '帮助中心',
);
}
/// 活动专区页面
static Widget promotionPage() {
return const WebViewPage(
url: promotionActivity,
title: '活动专区',
);
}
}
3.3 JavaScript 交互
Flutter 和 WebView 之间可以互相调用:
import 'package:flutter/dartz.dart';
class WebViewWithJSPage extends StatefulWidget {
final String url;
final String? title;
const WebViewWithJSPage({
super.key,
required this.url,
this.title,
});
State<WebViewWithJSPage> createState() => _WebViewWithJSPageState();
}
class _WebViewWithJSPageState extends State<WebViewWithJSPage> {
late final WebViewController _controller;
void initState() {
super.initState();
_initController();
}
void _initController() {
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (url) {
// 页面加载完成后,可以执行 JavaScript
_controller.runJavaScript('''
console.log('Flutter says: WebView loaded!');
''');
},
),
)
..loadRequest(Uri.parse(widget.url));
}
/// 调用 JavaScript 方法
Future<void> _callJavaScript() async {
// 调用网页中的 getUserInfo() 方法
final result = await _controller.runJavaScriptReturningResult(
'getUserInfo()',
);
debugPrint('JavaScript 返回: $result');
}
/// 向网页传递数据
Future<void> _sendDataToWeb() async {
// 传递 JSON 数据给网页
await _controller.runJavaScript('''
window.flutterData = {
userId: '12345',
token: 'abc123',
timestamp: ${DateTime.now().millisecondsSinceEpoch}
};
window.onFlutterDataReceived && window.onFlutterDataReceived(window.flutterData);
''');
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title ?? 'WebView')),
body: Column(
children: [
// 操作按钮
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
ElevatedButton(
onPressed: _callJavaScript,
child: const Text('调用 JS'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: _sendDataToWeb,
child: const Text('传数据给 JS'),
),
],
),
),
// WebView
Expanded(
child: WebViewWidget(controller: _controller),
),
],
),
);
}
}
3.4 在路由中使用 WebView
// 在 app_router.dart 中添加
GoRoute(
path: '/webview',
name: 'webView',
builder: (context, state) {
final url = state.uri.queryParameters['url'] ?? 'https://example.com';
final title = state.uri.queryParameters['title'];
return WebViewPage(url: url, title: title);
},
),
// 使用示例
// context.push('/webview?url=https://xxx.com&title=活动页面')
// 或者使用工厂方法
GoRoute(
path: '/privacy',
builder: (context, state) => WebViewPages.privacyPolicyPage(),
),
四、开发踩坑与挫折 😤
4.1 踩坑一:WebView 不显示
问题描述:
页面一片空白,什么都不显示。
排查过程:
- 检查 URL 是否正确
- 检查网络权限
- 检查 JavaScript 是否启用
解决方案:
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted) // 确保启用 JS
..loadRequest(Uri.parse(widget.url));
4.2 踩坑二:Android 11+ 无法加载 HTTP
问题描述:
Android 11+ 系统默认禁止加载 HTTP 链接。
解决方案:
在 android/app/src/main/AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 如果需要加载 HTTP(非 HTTPS),添加: -->
<application
android:usesCleartextTraffic="true"
...>
4.3 踩坑三:页面返回问题
问题描述:
按返回键直接退出 App,而不是返回 WebView 的上一页。
解决方案:
使用 PopScope + canGoBack():
PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (await _controller.canGoBack()) {
_controller.goBack();
} else if (context.mounted) {
Navigator.of(context).pop();
}
},
child: ...,
)
五、鸿蒙专属适配 🔧
5.1 鸿蒙设备权限配置
在鸿蒙设备的 module.json5 中配置网络权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
六、最终实现效果 📸
(此处附鸿蒙设备上成功运行的截图)
—

七、个人学习总结 📝
通过 WebView 的学习,我收获了很多:
- ✅ 学会了如何在 Flutter 中嵌入 H5 页面
- ✅ 学会了 WebView 的各种配置
- ✅ 学会了 Flutter 和 JavaScript 的交互
作者:上海某高校大一学生,Flutter 爱好者
发布时间:2026年4月
更多推荐



所有评论(0)