最近 2019 的 google io 大会开始了,之前的"蜂鸟"引擎也在 flutter 官网中出现了, 不过这次改了个名字叫 flutter-web
具体的使用步骤参考项目 readme 中的方式来使用
建议: 配置dart
,pub
,~/.pub-cache/bin
到环境变量
配置 webdev
git clone https://github.com/flutter/flutter_web.git
cd flutter_web/examples/hello_world/
flutter packages upgrade
flutter packages pub global activate webdev
运行
webdev serve
提示我们,在本地 8080 端口, 在浏览器打开 http://localhost:8080
默认的 main.dart 比较简单,只有一个 Text 控件
我这里修改一下 main.dart 文件,达到接近 flutter 移动项目 main.dart 的样子
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int counter = 0;
TextEditingController controller = TextEditingController();
void add() {
counter++;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(
children: [
// TextField(
// controller: controller,
// ),
Text(counter.toString()),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: add,
tooltip: 'push',
child: Icon(Icons.add),
),
);
}
@override
void initState() {
super.initState();
print("${this.runtimeType} initState");
}
@override
void dispose() {
print("${this.runtimeType} dispose");
super.dispose();
}
}
这里看到了第一个问题, 图标没有显示
然后简单试一下页面的交互
文字无法选中, 这个可以理解,因为是自绘引擎, 和网页不一样,文字无法选中是正常的
试一下文本输入
修改文件的 state 部分
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int counter = 0;
TextEditingController controller = TextEditingController();
var key = GlobalKey();
void add() {
counter++;
setState(() {});
ScaffoldState state = key.currentState;
state.showSnackBar(
SnackBar(
content: Text(controller.text),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: key,
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(
children: [
TextField(
controller: controller,
),
Text(counter.toString()),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: add,
tooltip: 'push',
child: Icon(Icons.add),
),
);
}
@override
void initState() {
super.initState();
print("${this.runtimeType} initState");
}
@override
void dispose() {
print("${this.runtimeType} dispose");
super.dispose();
}
}
加入了一个 TextField 控件,然后输入文本,接着将文本显示到 snackbar 中,接着点击按钮得到以下的样式
文本的输入等功能基本能实现
嗯,中文输入可用,直接用的是系统的输入法,不过输入框没有跟随
在 tooltip 显示的情况下拖动可以选择部分文本
另外测试了一下按钮的功能
copy paste 都无效,暂时没有和 macOS 系统的剪切板关联,其他系统的没测试,未知
使用系统的复制粘贴全选快捷键(cmd+c, cmd+p, cma+a)是可用的
简单截取一个图片,准备用于项目中,嗯,就是 google io 的演讲视频
可以看到图片,能够正常显示
目前为止的代码如下
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int counter = 0;
TextEditingController controller = TextEditingController();
var key = GlobalKey();
void add() {
counter++;
setState(() {});
ScaffoldState state = key.currentState;
state.showSnackBar(
SnackBar(
content: Text(controller.text),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: key,
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(
children: [
TextField(
controller: controller,
),
Text(
counter.toString(),
),
Image.network(
"https://raw.githubusercontent.com/kikt-blog/image/master/img/20190508104658.png"),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: add,
tooltip: 'push',
child: Icon(Icons.add),
),
);
}
@override
void initState() {
super.initState();
print("${this.runtimeType} initState");
}
@override
void dispose() {
print("${this.runtimeType} dispose");
super.dispose();
}
}
结论: 使用 Image.asset 失败了,没有图片显示 经群中大佬解说,可以显示
目前使用约定式目录结构, 和桌面引擎的方式一致
必须放入web/assets
目录下,不用在 pubspec 中声明
目录结构如下:
web
├── assets
│ └── images
│ └── 20190508104658.png
├── index.html
└── main.dart
插入控件
Image.asset(R.IMG_20190508104658_PNG),
/// generate by resouce_generator library, shouldn't edit.
class R {
/// ![preview](file:///private/tmp/flutter_web/examples/hello_world/web/assets/images/20190508104658.png)
static const String IMG_20190508104658_PNG = "images/20190508104658.png";
}
还是刚刚的图片, 这次经过 base64 编码后直接储存至 dart 文件中
然后通过如下的方式获取到项目中
import 'dart:convert';
import 'dart:typed_data';
Uint8List getImageList(String imageBase64) {
return base64.decode(imageBase64);
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_web/material.dart';
import 'const/resource.dart';
import 'img.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int counter = 0;
TextEditingController controller = TextEditingController();
var key = GlobalKey();
void add() {
counter++;
setState(() {});
ScaffoldState state = key.currentState;
state.showSnackBar(
SnackBar(
content: Text(controller.text),
),
);
}
static var divider = Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text("我是分割线"),
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 5,
),
),
);
@override
Widget build(BuildContext context) {
return Scaffold(
key: key,
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(
children: [
TextField(
controller: controller,
),
Text(
counter.toString(),
),
Image.network(
"https://raw.githubusercontent.com/kikt-blog/image/master/img/20190508104658.png"),
divider,
Image.asset(R.IMG_20190508104658_PNG),
divider,
Image.memory(getImageList(imageBase64)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: add,
tooltip: 'push',
child: Icon(Icons.add),
),
);
}
@override
void initState() {
super.initState();
print("${this.runtimeType} initState");
}
@override
void dispose() {
print("${this.runtimeType} dispose");
super.dispose();
}
}
将 Column 替换为 ListView
支持滚动
这里有一点要提,如果是刚进这个页面,鼠标的滚轮是无效的,也就是说,你需要在页面中随意点击一下才可以使用滚动滚动这个页面,似乎是为了让控件获得焦点
我将 ListView 设置为横向滚动,发生了错误,我将 TextField 注释掉以后,恢复了显示
并且可以正常横向滚动,在 mac 中也支持 shift+滚动的左右滚动
使用 print 方法在 dart 文件中输出日志
可以在 chrome 的开发者工具的 console 中看到, 目前表现基本与浏览器中的 console.log 方法输出一致
print("1 is int : ${1 is int}"); // true
print("1 is double : ${1 is double}"); // true
print("1.0 is int : ${1.0 is int}"); // true
print("1.0 is double : ${1.0 is double}"); // true
print(1.runtimeType); // int
print(1.0.runtimeType); // int
这一点和 flutter, dartVM 中表现不一样,和 js 表现一致
而 runtimeType 中 1 和 1.0 都是 int 类型
目前在编译过程中,如果发现了使用 dart:io 包的情况,就会自动忽略这个文件的编译
日志如下:
[WARNING] build_web_compilers:entrypoint on web/main.dart: Skipping compiling flutter_web.examples.hello_world|web/main.dart with ddc because some of its
transitive libraries have sdk dependencies that not supported on this platform:
flutter_web.examples.hello_world|lib/main.dart
https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings
目前没有成熟的插件系统,也没有完成与纯 flutter 插件的对接
据说可以调用 js 的库来获取一些结果,官方的解释
使用 webdev 打包
$ webdev build
webdev build
[INFO] build_web_compilers:entrypoint on web/main.dart: Running dart2js with --minify --packages=.package-eb297017792c41ff65511a11729f572e -oweb/main.dart.js web/main.dart
[INFO] build_web_compilers:entrypoint on web/main.dart: Dart2Js finished with:
Compiled 20,702,176 characters Dart to 4,249,785 characters JavaScript in 13.8 seconds
Dart file (web/main.dart) compiled to JavaScript: web/main.dart.js
[INFO] Running build completed, took 16.1s
[INFO] Caching finalized dependency graph completed, took 178ms
[INFO] Reading manifest at build/.build.manifest completed, took 13ms
[INFO] Deleting previous outputs in `build` completed, took 93ms
[INFO] Creating merged output dir `build` completed, took 780ms
[INFO] Writing asset manifest completed, took 2ms
[INFO] Succeeded after 17.2s with 9 outputs (2073 actions)
17 秒左右
在当前 build 文件夹下生成了一些文件
这些文件直接本地打开 index.html 是跑不起来的
我这里借助了一个轻量的 web 服务器来做这个事
serve build
打开后和运行一样
看一下 build 文件夹的大小, 这里我要惊叹一声!!! 我… 56m !!!
其中主要大小集中在packages/$sdk
中,有 51m, main.dart.js
有 1.2m ,这里因为我放入了那个 base64 的图片字符串充当图片来源, 这个 base64 的字符串在 txt 文件中是 3.2m,所以 main.dart.js 的大小我还算可以接受
assets 目录是 copy 过来的
使用 gz 格式压缩完有 11.4mb
所以这个称之为"开发者预览"是有道理的,后续看怎么优化大小吧,简单来说,这个大小即使在压缩完后也是不能接受的…
这里使用 web 开发者工具看看
整体是一个控件,看来是和 iOS android 一样,直接绘制的
右边看到有一个 input 控件,然后 tanslate 了很长的距离, 应该是用于和内部输入框做双向绑定,以实现复制粘贴,光标等操作的双向绑定关系
简单来说,有一些 bug 和不足
仓库在这, 查看 example/helloworld 目录
总结: 可用程度?暂时不可用
以上