最近 2019 的 google io 大会开始了,之前的"蜂鸟"引擎也在 flutter 官网中出现了, 不过这次改了个名字叫 flutter-web
<https://github.com/flutter/flutter_web>

具体的使用步骤参考项目 readme 中的方式来使用



文章目录

* 构建项目 <https://blog.csdn.net/qq_28478281/article/details/89955152#_5>
* 运行项目 <https://blog.csdn.net/qq_28478281/article/details/89955152#_18>
* 简单运行 <https://blog.csdn.net/qq_28478281/article/details/89955152#_20>
* 测试交互 <https://blog.csdn.net/qq_28478281/article/details/89955152#_121>
* 文本输入 <https://blog.csdn.net/qq_28478281/article/details/89955152#_130>
* 图片 <https://blog.csdn.net/qq_28478281/article/details/89955152#_224>
* 网络图片 <https://blog.csdn.net/qq_28478281/article/details/89955152#_226>
* 本地资源文件 <https://blog.csdn.net/qq_28478281/article/details/89955152#_330>
* 内存图片 <https://blog.csdn.net/qq_28478281/article/details/89955152#_365>
* 滚动控件 <https://blog.csdn.net/qq_28478281/article/details/89955152#_494>
* 日志 <https://blog.csdn.net/qq_28478281/article/details/89955152#_510>
* 几个问题需要注意 <https://blog.csdn.net/qq_28478281/article/details/89955152#_516>
* 数字的类型 <https://blog.csdn.net/qq_28478281/article/details/89955152#_518>
* dart:io 的问题
<https://blog.csdn.net/qq_28478281/article/details/89955152#dartio__534>
* 插件的使用 <https://blog.csdn.net/qq_28478281/article/details/89955152#_549>
* 打包 <https://blog.csdn.net/qq_28478281/article/details/89955152#_555>
* 查看一下 html 结构
<https://blog.csdn.net/qq_28478281/article/details/89955152#_html__602>
* 后记 <https://blog.csdn.net/qq_28478281/article/details/89955152#_612>


<>构建项目

建议: 配置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 <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<MyHomePage> { 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: <Widget>[ // 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<MyHomePage> { 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: <Widget>[ 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 的提示


拖动可以部分选择,但部分选择时的弹框没有出现


在 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<MyHomePage> { 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: <Widget>[ 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<MyHomePage> { 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: <Widget>[ 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 的问题

目前在编译过程中,如果发现了使用 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 的库来获取一些结果,官方的解释 <https://github.com/flutter/flutter_web#limitations>

<>打包

使用 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 JavaScriptin 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 outputsin `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

所以这个称之为"开发者预览"是有道理的,后续看怎么优化大小吧,简单来说,这个大小即使在压缩完后也是不能接受的…

<>查看一下 html 结构

这里使用 web 开发者工具看看



整体是一个控件,看来是和 iOS android 一样,直接绘制的

右边看到有一个 input 控件,然后 tanslate 了很长的距离, 应该是用于和内部输入框做双向绑定,以实现复制粘贴,光标等操作的双向绑定关系

<>后记

简单来说,有一些 bug 和不足

* Icons 的图标不显示
* 文本不能选中
* 输入框的交互太移动端了
* 不支持插件
* 打包太大了
仓库在这, 查看 example/helloworld 目录
<https://gitee.com/kikt/flutter_web_first_use/tree/master/examples/hello_world>

总结: 可用程度?暂时不可用

以上

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信