Web 开发支持
Flutter 对web应用开发支持尚处于早期,目前仍是 beta 版,并未发布正式版。因此,想要开发支持Web的应用,需要将SDK切换到Beta通道。
Flutter Web 架构图

环境准备
运行以下命令切换到Beta通道,使用最新版本的Flutter SDK并启用Web支持
flutter channel beta
flutter upgrade
flutter config --enable-web
确定本机已安装Chrome浏览器,然后执行以下命令,用以检查开发环境是否包含Web Server和Chrome
flutter devices
添加支持
要在已有项目中添加Web支持,进入项目根目录下,执行以下命令
flutter create .
判断当前环境
目前没有直接的API用于判断当前是否运行在浏览器上,但我们可以使用以下方法进行封装
创建如下文件

_api.dart内容如下
String getPlatformOS(){
throw UnsupportedError('No implementation of the connect api provided');
}
_html.dart内容如下
String getPlatformOS(){
return 'browser';
}
_io.dart内容如下
import 'dart:io';
String getPlatformOS(){
return Platform.operatingSystem;
}
environment.dart内容如下
/// 借助导包的方式,在不同平台上加载不同的库
import '_api.dart'
if (dart.library.io) '_io.dart'
if (dart.library.html) '_html.dart' as platform;
// 定义各平台枚举类型
enum Env{
Linux,MacOS,Windows,Android,IOS,Fuchsia,Browser,Unknown
}
class Environment{
static Env get current {
var os = platform.getPlatformOS();
switch(os){
case "linux":
return Env.Linux;
case "macos":
return Env.MacOS;
case "windows":
return Env.Windows;
case "android":
return Env.Android;
case "ios":
return Env.IOS;
case "fuchsia":
return Env.Fuchsia;
case "browser":
return Env.Browser;
default:
return Env.Unknown;
}
}
}
以上封装完成后,我们可以调用Environment.current方法来判断当前的运行平台,从而差异化处理代码
构建响应式UI
响应式应用程序会根据屏幕或窗口的大小和形状来布局其UI。这可以使应用程序在多种设备(如手表,手机,平板、笔记本电脑等)上运行。参见官方文档 创建响应式应用
Flutter中构建响应式UI,有两种基本方法:
- 使用
LayoutBuilder控件 - 使用
MediaQuery.of()方法
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: constraints.maxWidth < 480
? ContentView()
: Row(
children: [
SizedBox(
width: 200.0,
child: MenuBar()
),
Expanded(
child: ContentView(),
)
],
),
drawer: constraints.maxWidth < 480 ? Drawer(
child: MenuBar(),
) : null,
);
});
}
}
/// 菜单栏
class MenuBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: [
ListTile(
leading: Icon(Icons.looks_one),
title: Text("First Link"),
),
ListTile(
leading: Icon(Icons.looks_two),
title: Text("Second Link"),
)
]
);
}
}
/// 主视图
class ContentView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: 100,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 130.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemBuilder: (context, i) => Card(
child: Center(
child: Padding(
padding: EdgeInsets.all(8.0), child: Text("grid item $i")
)
)
)
);
}
}
除了使用LayoutBuilder,也可以使用MediaQuery.of(context).size.width来获取屏幕的宽度信息,从而实现不同的布局。
另外,我们还可以借助OrientationBuilder控件来处理横竖屏切换的问题,其中orientation参数是一个枚举类型
OrientationBuilder(
builder: (context, orientation) {
return Container();
});
Orientation.landscape横屏(宽大于高)Orientation.portrait竖屏(高大于宽)
当app运行到PC上时,由于PC的屏幕通常是宽大于高,故而orientation参数的值为Orientation.landscape
当我们想要编写响应式UI,以兼容Web版时,可以根据当前屏幕信息自行实现差异化UI,也可借助第三方库,例如responsive_framework
使用NavigationRail
NavigationRail是一个侧边导航条,在Flutter 1.17 版本加入,使用前请升级SDK。与BottomNavigationBar不同,这个控件主要用于大屏设备的UI布局,如桌面程序、Web 或者平板设备等,而像手机这样的竖屏小屏设备,更适合使用 BottomNavigationBar 来实现导航栏。

NavigationRail属性列表
| 属性 | 类型 | 简述 |
|---|---|---|
| backgroundColor | Color |
背景色 |
| extended | bool |
表示NavigationRail是否展开。minExtendedWidth可设置展开状态下的最小宽度。如果处于展开状态,那么标签类型必须为NavigationRailLabelType.none |
| leading | Widget |
位于NavigationRail上方的小控件 |
| trailing | Widget |
位于NavigationRail下方的小控件 |
| destinations | List<NavigationRailDestination> |
定义排列在导航栏中的导航条 |
| selectedIndex | int |
当前选中的导航条索引 |
| onDestinationSelected | ValueChanged<int> |
选择导航条时回调 |
| elevation | double |
z 轴方向上的高度 |
| groupAlignment | double |
垂直方向上的对齐方式 |
| labelType | NavigationRailLabelType |
为未展开的默认NavigationRail定义标签的布局和行为 |
| unselectedLabelTextStyle | TextStyle |
标签未选中时的文字样式 |
| selectedLabelTextStyle | TextStyle |
标签选中时的文字样式 |
| unselectedIconTheme | IconThemeData |
图标未选中时的样式 |
| selectedIconTheme | IconThemeData |
图标选中时的样式 |
| minWidth | double |
最小宽度 |
| minExtendedWidth | double |
展开时的最小宽度 |
示例
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: <Widget>[
NavigationRail(
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.selected,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
),
VerticalDivider(thickness: 1, width: 1),
// 主视图
Expanded(
child: Center(
child: Text('selectedIndex: $_selectedIndex'),
),
)
],
),
);
}
}
提示:如果要实现上图中的文字效果,可以使用旋转布局RotatedBox,对Text进行旋转
RotatedBox(
quarterTurns: 1,
child: const Text('Hello World!'),
)
RotatedBox是一个将其子控件旋转四分之一圈的整数倍的布局控件(quarterTurns: 1表示顺时针旋转90度)。与Transform不同的是,Transform在绘制时进行变换,而该控件在布局之前就进行了旋转,这意味着整个旋转后的框体只占用旋转后的子控件所需的空间。
Web 开发案例
简单博客管理系统。共四大主页面,一个详情页面
- 首页
- 文章管理
- 查看
- 分类管理
- 写博客
注意,目前Web版使用多行输入框时,存在鼠标光标无法定位的问题,需要开启基于画布的文本测量功能,见issues 33523
添加编译命令--release --dart-define=FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT=true
关于Flutter中的图表库
charts_flutter 用Dart原生编写的Material Design数据可视化库,由Google提供
fl_chart 一个更炫酷的数据可视化库
syncfusion_flutter_charts 用Dart原生编写的数据可视化库,用于创建美观,高性能的图表。但这是一个收费的库!
关于Flutter的Markdown支持
flutter_markdown 该库是Flutter官方提供的支持
markdown_widget 一个第三方库,能支持TOC功能和代码高亮
Web 版路径处理
使用Flutter开发web版时,有时候需要通过浏览器URL路径来跳转相关的路由页面,这时候就需要实现onGenerateRoute方法来处理路由问题。另一方面,当需要实现超链接跳转时,还需要使用官方提供的url_launcher 库
公众号“编程之路从0到1”
