Flutter Web 评测第一篇
文章目录
最近 2019 的 google io 大会开始了,之前的"蜂鸟"引擎也在 flutter 官网中出现了, 不过这次改了个名字叫
具体的使用步骤参考项目 readme 中的方式来使用
构建项目
建议: 配置dart
,pub
,~/.pub-cache/bin
到环境变量
配置 webdev
1git clone https://github.com/flutter/flutter_web.git
2cd flutter_web/examples/hello_world/
3flutter packages upgrade
4flutter packages pub global activate webdev
运行项目
简单运行
运行
1webdev serve
提示我们,在本地 8080 端口, 在浏览器打开 http://localhost:8080
默认的 main.dart 比较简单,只有一个 Text 控件
我这里修改一下 main.dart 文件,达到接近 flutter 移动项目 main.dart 的样子
1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter_web/material.dart';
6
7void main() {
8 runApp(MyApp());
9}
10
11class MyApp extends StatelessWidget {
12 // This widget is the root of your application.
13 @override
14 Widget build(BuildContext context) {
15 return MaterialApp(
16 title: 'Flutter Demo',
17 theme: ThemeData(
18 primarySwatch: Colors.blue,
19 ),
20 home: MyHomePage(title: 'Flutter Demo Home Page'),
21 );
22 }
23}
24
25class MyHomePage extends StatefulWidget {
26 MyHomePage({Key key, this.title}) : super(key: key);
27
28 final String title;
29
30 @override
31 _MyHomePageState createState() => _MyHomePageState();
32}
33
34class _MyHomePageState extends State<MyHomePage> {
35 int counter = 0;
36 TextEditingController controller = TextEditingController();
37
38 void add() {
39 counter++;
40 setState(() {});
41 }
42
43 @override
44 Widget build(BuildContext context) {
45 return Scaffold(
46 appBar: AppBar(
47 title: Text(widget.title),
48 ),
49 body: Container(
50 child: Column(
51 children: <Widget>[
52 // TextField(
53 // controller: controller,
54 // ),
55 Text(counter.toString()),
56 ],
57 ),
58 ),
59 floatingActionButton: FloatingActionButton(
60 onPressed: add,
61 tooltip: 'push',
62 child: Icon(Icons.add),
63 ),
64 );
65 }
66
67 @override
68 void initState() {
69 super.initState();
70 print("${this.runtimeType} initState");
71 }
72
73 @override
74 void dispose() {
75 print("${this.runtimeType} dispose");
76 super.dispose();
77 }
78}
这里看到了第一个问题, 图标没有显示
测试交互
然后简单试一下页面的交互
遇到了第二个问题
文字无法选中, 这个可以理解,因为是自绘引擎, 和网页不一样,文字无法选中是正常的
文本输入
试一下文本输入
修改文件的 state 部分
1
2class MyHomePage extends StatefulWidget {
3 MyHomePage({Key key, this.title}) : super(key: key);
4
5 final String title;
6
7 @override
8 _MyHomePageState createState() => _MyHomePageState();
9}
10
11class _MyHomePageState extends State<MyHomePage> {
12 int counter = 0;
13 TextEditingController controller = TextEditingController();
14 var key = GlobalKey();
15 void add() {
16 counter++;
17 setState(() {});
18 ScaffoldState state = key.currentState;
19 state.showSnackBar(
20 SnackBar(
21 content: Text(controller.text),
22 ),
23 );
24 }
25
26 @override
27 Widget build(BuildContext context) {
28 return Scaffold(
29 key: key,
30 appBar: AppBar(
31 title: Text(widget.title),
32 ),
33 body: Container(
34 child: Column(
35 children: <Widget>[
36 TextField(
37 controller: controller,
38 ),
39 Text(counter.toString()),
40 ],
41 ),
42 ),
43 floatingActionButton: FloatingActionButton(
44 onPressed: add,
45 tooltip: 'push',
46 child: Icon(Icons.add),
47 ),
48 );
49 }
50
51 @override
52 void initState() {
53 super.initState();
54 print("${this.runtimeType} initState");
55 }
56
57 @override
58 void dispose() {
59 print("${this.runtimeType} dispose");
60 super.dispose();
61 }
62}
加入了一个 TextField 控件,然后输入文本,接着将文本显示到 snackbar 中,接着点击按钮得到以下的样式
文本的输入等功能基本能实现
嗯,中文输入可用,直接用的是系统的输入法,不过输入框没有跟随
长按输入框位置无效, 双击可以看到 tooltip 的提示
拖动可以部分选择,但部分选择时的弹框没有出现
在 tooltip 显示的情况下拖动可以选择部分文本
另外测试了一下按钮的功能 copy paste 都无效,暂时没有和 macOS 系统的剪切板关联,其他系统的没测试,未知
使用系统的复制粘贴全选快捷键(cmd+c, cmd+v, cma+a)是可用的
图片
网络图片
简单截取一个图片,准备用于项目中,嗯,就是 google io 的演讲视频
可以看到图片,能够正常显示
目前为止的代码如下
1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter_web/material.dart';
6
7void main() {
8 runApp(MyApp());
9}
10
11class MyApp extends StatelessWidget {
12 // This widget is the root of your application.
13 @override
14 Widget build(BuildContext context) {
15 return MaterialApp(
16 title: 'Flutter Demo',
17 theme: ThemeData(
18 primarySwatch: Colors.blue,
19 ),
20 home: MyHomePage(title: 'Flutter Demo Home Page'),
21 );
22 }
23}
24
25class MyHomePage extends StatefulWidget {
26 MyHomePage({Key key, this.title}) : super(key: key);
27
28 final String title;
29
30 @override
31 _MyHomePageState createState() => _MyHomePageState();
32}
33
34class _MyHomePageState extends State<MyHomePage> {
35 int counter = 0;
36 TextEditingController controller = TextEditingController();
37 var key = GlobalKey();
38 void add() {
39 counter++;
40 setState(() {});
41 ScaffoldState state = key.currentState;
42 state.showSnackBar(
43 SnackBar(
44 content: Text(controller.text),
45 ),
46 );
47 }
48
49 @override
50 Widget build(BuildContext context) {
51 return Scaffold(
52 key: key,
53 appBar: AppBar(
54 title: Text(widget.title),
55 ),
56 body: Container(
57 child: Column(
58 children: <Widget>[
59 TextField(
60 controller: controller,
61 ),
62 Text(
63 counter.toString(),
64 ),
65 Image.network(
66 "https://raw.githubusercontent.com/kikt-blog/image/master/img/20190508104658.png"),
67 ],
68 ),
69 ),
70 floatingActionButton: FloatingActionButton(
71 onPressed: add,
72 tooltip: 'push',
73 child: Icon(Icons.add),
74 ),
75 );
76 }
77
78 @override
79 void initState() {
80 super.initState();
81 print("${this.runtimeType} initState");
82 }
83
84 @override
85 void dispose() {
86 print("${this.runtimeType} dispose");
87 super.dispose();
88 }
89}
本地资源文件
结论: 使用 Image.asset 失败了,没有图片显示 经群中大佬解说,可以显示
目前使用约定式目录结构, 和桌面引擎的方式一致
必须放入web/assets
目录下,不用在 pubspec 中声明
目录结构如下:
1web
2├── assets
3│ └── images
4│ └── 20190508104658.png
5├── index.html
6└── main.dart
插入控件
1 Image.asset(R.IMG_20190508104658_PNG),
1/// generate by resouce_generator library, shouldn't edit.
2class R {
3 /// ![preview](file:///private/tmp/flutter_web/examples/hello_world/web/assets/images/20190508104658.png)
4 static const String IMG_20190508104658_PNG = "images/20190508104658.png";
5}
内存图片
还是刚刚的图片, 这次经过 base64 编码后直接储存至 dart 文件中
然后通过如下的方式获取到项目中
1import 'dart:convert';
2
3import 'dart:typed_data';
4
5Uint8List getImageList(String imageBase64) {
6 return base64.decode(imageBase64);
7}
1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter_web/material.dart';
6
7import 'const/resource.dart';
8import 'img.dart';
9
10void main() {
11 runApp(MyApp());
12}
13
14class MyApp extends StatelessWidget {
15 // This widget is the root of your application.
16 @override
17 Widget build(BuildContext context) {
18 return MaterialApp(
19 title: 'Flutter Demo',
20 theme: ThemeData(
21 primarySwatch: Colors.blue,
22 ),
23 home: MyHomePage(title: 'Flutter Demo Home Page'),
24 );
25 }
26}
27
28class MyHomePage extends StatefulWidget {
29 MyHomePage({Key key, this.title}) : super(key: key);
30
31 final String title;
32
33 @override
34 _MyHomePageState createState() => _MyHomePageState();
35}
36
37class _MyHomePageState extends State<MyHomePage> {
38 int counter = 0;
39 TextEditingController controller = TextEditingController();
40 var key = GlobalKey();
41 void add() {
42 counter++;
43 setState(() {});
44 ScaffoldState state = key.currentState;
45 state.showSnackBar(
46 SnackBar(
47 content: Text(controller.text),
48 ),
49 );
50 }
51
52 static var divider = Container(
53 padding: const EdgeInsets.symmetric(vertical: 10),
54 child: Text("我是分割线"),
55 decoration: BoxDecoration(
56 border: Border.all(
57 color: Colors.blue,
58 width: 5,
59 ),
60 ),
61 );
62
63 @override
64 Widget build(BuildContext context) {
65 return Scaffold(
66 key: key,
67 appBar: AppBar(
68 title: Text(widget.title),
69 ),
70 body: Container(
71 child: Column(
72 children: <Widget>[
73 TextField(
74 controller: controller,
75 ),
76 Text(
77 counter.toString(),
78 ),
79 Image.network(
80 "https://raw.githubusercontent.com/kikt-blog/image/master/img/20190508104658.png"),
81 divider,
82 Image.asset(R.IMG_20190508104658_PNG),
83 divider,
84 Image.memory(getImageList(imageBase64)),
85 ],
86 ),
87 ),
88 floatingActionButton: FloatingActionButton(
89 onPressed: add,
90 tooltip: 'push',
91 child: Icon(Icons.add),
92 ),
93 );
94 }
95
96 @override
97 void initState() {
98 super.initState();
99 print("${this.runtimeType} initState");
100 }
101
102 @override
103 void dispose() {
104 print("${this.runtimeType} dispose");
105 super.dispose();
106 }
107}
滚动控件
将 Column 替换为 ListView
支持滚动
这里有一点要提,如果是刚进这个页面,鼠标的滚轮是无效的,也就是说,你需要在页面中随意点击一下才可以使用滚动滚动这个页面,似乎是为了让控件获得焦点
我将 ListView 设置为横向滚动,发生了错误,我将 TextField 注释掉以后,恢复了显示
并且可以正常横向滚动,在 mac 中也支持 shift+滚动的左右滚动
日志
使用 print 方法在 dart 文件中输出日志
可以在 chrome 的开发者工具的 console 中看到, 目前表现基本与浏览器中的 console.log 方法输出一致
几个问题需要注意
数字的类型
1 print("1 is int : ${1 is int}"); // true
2 print("1 is double : ${1 is double}"); // true
3 print("1.0 is int : ${1.0 is int}"); // true
4 print("1.0 is double : ${1.0 is double}"); // true
5
6 print(1.runtimeType); // int
7 print(1.0.runtimeType); // int
这一点和 flutter, dartVM 中表现不一样,和 js 表现一致
而 runtimeType 中 1 和 1.0 都是 int 类型
dart:io 的问题
目前在编译过程中,如果发现了使用 dart:io 包的情况,就会自动忽略这个文件的编译
日志如下:
1[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
2transitive libraries have sdk dependencies that not supported on this platform:
3
4flutter_web.examples.hello_world|lib/main.dart
5
6https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings
插件的使用
目前没有成熟的插件系统,也没有完成与纯 flutter 插件的对接
据说可以调用 js 的库来获取一些结果,
打包
使用 webdev 打包
$ webdev build
1webdev build
2[INFO] build_web_compilers:entrypoint on web/main.dart: Running dart2js with --minify --packages=.package-eb297017792c41ff65511a11729f572e -oweb/main.dart.js web/main.dart
3[INFO] build_web_compilers:entrypoint on web/main.dart: Dart2Js finished with:
4
5Compiled 20,702,176 characters Dart to 4,249,785 characters JavaScript in 13.8 seconds
6Dart file (web/main.dart) compiled to JavaScript: web/main.dart.js
7[INFO] Running build completed, took 16.1s
8[INFO] Caching finalized dependency graph completed, took 178ms
9[INFO] Reading manifest at build/.build.manifest completed, took 13ms
10[INFO] Deleting previous outputs in `build` completed, took 93ms
11[INFO] Creating merged output dir `build` completed, took 780ms
12[INFO] Writing asset manifest completed, took 2ms
13[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
所以这个称之为"开发者预览"是有道理的,后续看怎么优化大小吧,简单来说,这个大小即使在压缩完后也是不能接受的...
查看一下 html 结构
这里使用 web 开发者工具看看
整体是一个控件,看来是和 iOS android 一样,直接绘制的
右边看到有一个 input 控件,然后 tanslate 了很长的距离, 应该是用于和内部输入框做双向绑定,以实现复制粘贴,光标等操作的双向绑定关系
后记
简单来说,有一些 bug 和不足
- Icons 的图标不显示
- 文本不能选中
- 输入框的交互太移动端了
- 不支持插件
- 打包太大了
仓库在这, 查看
总结: 可用程度?暂时不可用
以上