Flutter Web 核心优势
Flutter Web 允许使用同一套代码库构建 Android、iOS 和 Web 应用,在保持高性能的同时提供一致的用户体验。对于追求像素级还原的 UI 开发者来说,跨平台方案往往意味着妥协,但 Flutter 在这方面表现相当出色。
环境搭建
启用 Web 支持
首先需要确保 Flutter SDK 已开启 Web 支持。执行以下命令检查设备并创建项目:
flutter config --enable-web
flutter devices
flutter create my_web_app
cd my_web_app
flutter run -d chrome
项目结构
一个标准的 Flutter Web 项目通常包含以下目录结构,便于管理不同平台的逻辑:
my_web_app/
├── lib/
│ ├── main.dart # 主入口
│ ├── home_screen.dart # 主屏幕
│ ├── components/ # 组件
│ ├── services/ # 服务层
│ └── web/ # Web 特定文件
├── web/
│ ├── index.html # HTML 入口
│ └── manifest.json # PWA 配置
├── icons/ # 图标资源
└── pubspec.yaml # 依赖配置
基础实现
简单的 Web 应用
创建一个基础的计数器应用是理解 Flutter 状态管理的起点。注意 setState 的使用时机,它负责触发 UI 重绘。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Web Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Web Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
Web 特定功能
响应式布局
Web 端屏幕尺寸多变,使用 LayoutBuilder 可以根据视口宽度动态切换布局,这是适配移动端、平板和桌面的关键。
class ResponsiveLayout extends StatelessWidget {
final Widget mobileLayout;
final Widget tabletLayout;
final Widget desktopLayout;
const ResponsiveLayout({
Key? key,
required this.mobileLayout,
required this.tabletLayout,
required this.desktopLayout,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return mobileLayout;
} else if (constraints.maxWidth < 1024) {
return tabletLayout;
} else {
return desktopLayout;
}
},
);
}
}
// 使用示例
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ResponsiveLayout(
mobileLayout: MobileHome(),
tabletLayout: TabletHome(),
desktopLayout: DesktopHome(),
);
}
}
平台检测
当需要针对特定平台调用原生能力时,可以使用 foundation.dart 中的常量进行判断。这样可以在一份代码中处理多端差异。
import 'package:flutter/foundation.dart';
class PlatformAwareWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (kIsWeb) {
// Web 特定实现
return WebSpecificWidget();
} else if (Platform.isAndroid) {
// Android 特定实现
return AndroidSpecificWidget();
} else if (Platform.isIOS) {
// iOS 特定实现
return IOSSpecificWidget();
} else {
// 通用实现
return CommonWidget();
}
}
}
与 Web API 交互
虽然 Dart 提供了丰富的 Web API 封装,但在某些场景下仍需直接操作 DOM 或调用 JavaScript 函数。这里通过 dart:js 包实现互操作。
import 'dart:js' as js;
class WebInteropWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
// 调用 JavaScript 函数
js.context.callMethod('alert', ['Hello from Flutter!']);
},
child: const Text('Show Alert'),
),
ElevatedButton(
onPressed: () {
// 访问 DOM
js.context['document'].callMethod('title', ['Flutter Web App']);
},
child: const Text('Change Title'),
),
],
);
}
}
性能优化
代码分割
对于大型应用,路由级别的代码分割能显著减少初始加载时间。利用 MaterialApp 的 routes 属性定义页面路由即可。
// main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => HomePage(),
'/details': (context) => DetailsPage(),
},
);
}
}
// details_page.dart
class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: const Center(child: Text('Details Page')),
);
}
}
图片优化
网络图片加载可能影响首屏速度,添加加载进度条和错误占位图能提升用户体验。
class OptimizedImage extends StatelessWidget {
final String src;
final double? width;
final double? height;
const OptimizedImage({
Key? key,
required this.src,
this.width,
this.height,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Image.network(
src,
width: width,
height: height,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return Container(
width: width,
height: height,
color: Colors.grey[200],
child: const Icon(Icons.error),
);
},
);
}
}
PWA 支持
配置 manifest.json
将应用配置为渐进式 Web 应用(PWA),使其具备离线能力和桌面快捷方式。修改 web/manifest.json 文件。
{
"name": "Flutter Web App",
"short_name": "FlutterApp",
"description": "A Flutter Web application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4285f4",
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
配置 service_worker.js
Service Worker 负责缓存策略,确保离线访问时的基本功能可用。
// service_worker.js
const CACHE_NAME = 'flutter-app-cache';
const ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/icons/Icon-192.png',
'/icons/Icon-512.png',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
部署
构建 Web 应用
生产环境构建推荐使用 canvaskit 渲染器以获得更好的图形性能,或者使用 html 渲染器以减小体积。
# 开发模式
flutter build web
# 生产模式(优化)
flutter build web --release --web-renderer canvaskit
# 生产模式(体积更小)
flutter build web --release --web-renderer html
部署到静态网站托管
构建完成后,生成的 build/web 目录可直接部署至各类静态托管服务,例如 Firebase Hosting、Netlify、Vercel 或 GitHub Pages。
