Flutter 在去年的时候就有一个第三方的桌面引擎, 是用 golang 开发的

Github 地址是:https://github.com/go-flutter-desktop/go-flutter

目前在 mac,linux,windows 均可用, 作为一个 mac 用户, 除了 retina 下字显得有点小, 感觉没有单独适配外, 总体感觉是优于官方的 desktop 引擎的

另外我是真实的 golang 脑残粉, 我觉得 golang 这东西真的太好了, 用 golang, 准不会错

开发环境

需要的开发环境, 因为我是 MacOS, 我以 macOS 为例,其他的请参考对应的系统

  1. Xcode 命令行体系, 这个东西包含很多开发套件(Git), 无论你是否用 XCode 开发,都建议装一个…
  2. Flutter 环境和配套工具, 这个跑不掉,作为 flutter 开发者…
  3. go 语言环境(使用 brew 安装), 1.12+, IDE 用 Jetbrains 家的 goland (你用 VSCode 的话看你自己的情况)

环境安装

对 flutter 桌面版本感兴趣的一定接触过 flutter 开发, 我就默认你有 flutter 全套开发环境

go 语言环境安装

$ brew install go

如果你的 go 比较老, 请升级,使用$ brew upgrade go

配置 GOPATH 环境变量

$ vi ~/.bash_profile

export GOPATH=~/code/go # 这个是必须配置的, 等号后的部分根据你的情况修改, 简单来说里面放的是你自己的代码,不是go的SDK,不是go的SDK,不是go的SDK, 具体的话是你 go 语言的三方库源码/自己写的go代码/中间产物/应用程序所在的目录
PATH=$PATH:$GOPATH/bin  # 这个是选配, 但是强烈建议配置,不然以后的go工具链需要全路径引用

你下载的 go 相关的东西会被装在这个文件夹里

go-flutter 的环境

需要使用一个叫做 hover 的工具, 这个工具是由 go 编写的, 编译打包运行都使用这个工具

$ go get -u github.com/go-flutter-desktop/hover, 这样这个工具会被安装到$GOPATH/bin目录下

20190704163802.png

当你可以直接在命令行输入 hover 可以出现如下情况时就说明可用了

➜  ~ hover -h
Hover helps developers to release Flutter applications on desktop.

Usage:
  hover [flags]
  hover [command]

Available Commands:
  build       Build a desktop release
  help        Help about any command
  init        Initialize a flutter project to use go-flutter
  run         Build and start a desktop release, with hot-reload support

Flags:
  -h, --help   help for hover

Use "hover [command] --help" for more information about a command.
➜  ~

安装 hover 出现问题的话可以参考这里

运行 example

官方提供了几个 example: https://github.com/go-flutter-desktop/examples.git

cd /tmp
git clone https://github.com/go-flutter-desktop/examples.git flutter-examples
cd flutter-examples/pointer_demo
flutter pub get
hover run

通过以上几个步骤就可以把项目跑起来了

20190704165918.png 是一个关于鼠标移入移出监听的 demo

将原项目改为 desktop

官方说明文档是这样的, 不想看英文的, 可以跳过官方文档直接看我的中文说明

20190705150032.png

这里要注意, 因为插件系统的原因, 如果不是纯 dart 插件, 则插件内容不能用

我模拟一下这个过程, 创建一个+++的 helloworld 工程, 你如果是要改造已有项目, 则应该 cd 到你的 flutter 的根目录进行 $ hover init 项目url的操作, 这里根据官方说, url 无所谓, 后面可改

flutter create flutter_example_1
cd flutter_example_1
flutter pub get
hover init github.com/Caijinglong/flutter-go-example # 初始化 desktop 工程

这时候运行项目$ hover run会有一个提示: Target file "lib/main_desktop.dart" not found.

我们查询可知, 可能是考虑到兼容性的问题, go 引擎的项目使用 main_desktop.dart 作为入口, 我们创建一个文件

main_desktop.dart:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

import 'main.dart';

void main() {
  debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  runApp(MyApp());
}

$ hover run

20190704171705.png

这样项目就跑起来了

测试基础的项目

测试下常用的几项:

  1. 事件响应
  2. ListView
  3. 图片
  4. 输入框情况

事件响应

点击事件是可行的,数字可加, 说明鼠标事件能响应, 其他的长按双击等等都是 flutter 实现的, 理论上就不需要测试了

最开始的官方 demo 中有鼠标移入移出事件

ListView 滚动

import 'package:flutter/material.dart';

class ListViewPage extends StatefulWidget {
  @override
  _ListViewPageState createState() => _ListViewPageState();
}

class _ListViewPageState extends State<ListViewPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView.builder(
        itemBuilder: _buildItem,
      ),
    );
  }

  Widget _buildItem(BuildContext context, int index) {
    return ListTile(
      title: Text(index.toString()),
    );
  }
}

Kapture 2019-07-04 at 17.26.43.gif

没有移动端的惯性, 可以响应鼠标滚轮上下

改成横向的

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView.builder(
        itemBuilder: _buildItem,
        scrollDirection: Axis.horizontal,
      ),
    );
  }

横向同样没惯性, shift+滚动可以横向

图片

网络图片

import 'package:flutter/material.dart';

class ImagePage extends StatefulWidget {
  @override
  _ImagePageState createState() => _ImagePageState();
}

class _ImagePageState extends State<ImagePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView(
        children: <Widget>[
          Container(
            width: 500,
            height: 500,
            child: Image.network(
                "https://raw.githubusercontent.com/kikt-blog/image/master/img/20190704171705.png"),
          ),
        ],
      ),
    );
  }
}

网络图片可行

20190704173327.png

文件

File 的图片, 直接使用本地图片就可以了, 我因为是 mac,所以是这样的,windows 可能是c:\\XXXX\\XXX\\X.jpg

Container(
  width: 500,
  height: 500,
  child: Image.file(File("/Users/cai/Desktop/auto-angle.jpg")),
),

20190705092501.png

内存

memory , 这里需要模拟一下

读取上面的文件,然后转为 Uint8List

Container(
  width: 500,
  height: 500,
  child: Image.memory(
    Uint8List.fromList(
      File("/Users/cai/Desktop/auto-angle.jpg").readAsBytesSync(),
    ),
  ),
),

资产

asset: 这种方式的加载我印象中去年这个引擎需要使用约定式文件夹, 与 flutter-web 的方式类似
而现在不需要这种方式了, 直接与 flutter 官方的方式一致,只需要在 pubspec.yaml 中配置即可

flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/

20190705102154.png

Container(
  width: 500,
  height: 500,
  child: Image.asset(R.ASSETS_HAVE_EXIF_JPG),
),
/// generate by resouce_generator library, shouldn't edit.
class R {
  /// ![preview](file:///private/tmp/flutter-go-example/./assets/have-exif.jpg)
  static const String ASSETS_HAVE_EXIF_JPG = "assets/have-exif.jpg";
}

这里插入一句, 图片会根据 exif 信息旋转至正确的方向 20190705102408.png

但是图片多了后 ListView 的滚动性能似乎变差了

输入框

使用 Material 体系的 TextField 作为测试, Cupertino 和 Widget 体系的输入框请自行测试吧

有如下几个测试方向(有其他的需求可说, 我会加入)

  • 输入响应
  • 显示行为
  • 系统快捷键
  • 鼠标行为

输入响应

这个很好理解, 就是用键盘能否输入字符… 因为 flutter 上的官方的 plugin 就没法输入(我都是道听途说)

import 'package:flutter/material.dart';

class InputPage extends StatefulWidget {
  @override
  _InputPageState createState() => _InputPageState();
}

class _InputPageState extends State<InputPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('input')),
      body: Container(
        child: TextField(),
      ),
    );
  }
}

20190705102958.png

输入英文还算正常

试试中文:

20190705103106.png

文字位置正常,输入框没跟随

显示行为

单行没问题, 试试多行, 直接回车不行 我们需要将 TextField 设置为多行, 我这里分别设置 1050

20190705103327.png

50 行的话,一页放不下,滚动也算正常 Kapture 2019-07-05 at 10.34.23.gif

但是这里有一个问题, 中英文混合的情况下, 水滴不正常

20190705103537.png

开头和结尾都是英文则没问题, 都是中文同理 20190705103646.png 20190705103635.png

系统快捷键+鼠标行为

常用的几个快捷键(复制,粘贴,全选)都是 OK 的, 基本行为和正常的输入框完全吻合,这里要给好评, 比官方桌面引擎好用多了, 其他系统的请自行测试

Kapture 2019-07-05 at 10.37.44.gif

鼠标行为顺便一起测试了,基本符合正常的操作习惯

插件

这个版本的插件和官方的不一样, 需要用 golang 去写, 而不是各自平台的, 当然如果各自平台有特殊的 api, 也需要使用 golang 去调用

总体有如下几个步骤:

  1. 创建插件
  2. 编写代码(go+dart)
  3. 引入插件

官方文档在此: https://hover.build/docs/create-a-plugin/

创建并编写插件

go 端

打开 goland, 或者其他的什么编辑器

具体的 golang 知识没法展开讲解,

可以理解为在 gopath 的 src 目录下创建一个包, 大部分情况下模仿别人, 建议放在 github.com 目录下

$ mkdir -p src/github.com/caijinglong/go-flutter-plugin/version

创建一个目录, 这个就是我插件的文件夹

version.go:

package version

import (
    "github.com/go-flutter-desktop/go-flutter"
    "github.com/go-flutter-desktop/go-flutter/plugin"
)

const (
    channelName = "top.kikt/go/version"
    getVersion  = "getVersion"
)

type VersionPlugin struct{}

var _ flutter.Plugin = &VersionPlugin{}

func (VersionPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
    channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
    channel.HandleFunc(getVersion, getVersionFunc)
    return nil;
}

func getVersionFunc(arguments interface{}) (reply interface{}, err error) {
    return "0.0.1", nil
}

这个文件就是我们的插件, 必须要有的是结构体声明, 初始化插件的方法

下面那个getVersionFunc就是我们处理的方法, 这里可以使用 golang 编程,返回你需要通过 golang 获取的数据或任何东西, 我这里返回了一个简单的字符串

dart 端

import 'package:flutter/services.dart';

class GetVersionPlugin {
  static const _channel = const MethodChannel("top.kikt/go/version");

  static Future<String> get version async => _channel.invokeMethod("getVersion");
}

发布插件

为了让我们的 flutter 应用可以找到这个插件, 需要一些配置

go-flutter 使用的是 go module 的方案管理的 go 包

我们需要如下几步

cd $GOPATH/src/github.com/caijinglong/go-flutter-plugin/version
export GO111MODULE=on
go mod init github.com/caijinglong/go-flutter-plugin/version
go mod tidy

目前我们的目录结构是这样的

/Users/cai/code/go/src/github.com/caijinglong/go-flutter-plugin/version
├── go.mod
├── go.sum
└── version.go

要想发布, 其实得发布到 github 上, 这样别人才能访问, 我们目前不这么做, 仅本地使用

引入插件

这里需要修改 desktop/cmd 目录下的 options.go 文件

package main

import (
    "github.com/caijinglong/go-flutter-plugin/version"
    "github.com/go-flutter-desktop/go-flutter"
)

var options = []flutter.Option{
    flutter.WindowInitialDimensions(800, 1280),
    flutter.AddPlugin(version.VersionPlugin{}),
}

这时候重新运行下项目会报一个错

build github.com/Caijinglong/flutter-go-example/desktop/cmd: cannot load github.com/caijinglong/go-flutter-plugin/version: cannot find module providing package github.com/caijinglong/go-flutter-plugin/version

这个是因为插件没有发布到 github 所致, 我们先在本地测试下, 需要按照官方文档修改一下

module github.com/Caijinglong/flutter-go-example/desktop

go 1.12

require (
  github.com/go-flutter-desktop/go-flutter v0.24.1
  github.com/pkg/errors v0.8.1
  .com/stretchr/objx v0.2.0 // indirect
)

replace github.com/caijinglong/go-flutter-plugin/version => /Users/cai/code/go/src/github.com/caijinglong/go-flutter-plugin/version // 添加这行

接着就可以运行了

运行

20190705111417.png

代码如下:

import 'package:flutter/material.dart';

import 'get_version_plugin.dart';

class PluginPage extends StatefulWidget {
  @override
  _PluginPageState createState() => _PluginPageState();
}

class _PluginPageState extends State<PluginPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: FutureBuilder<String>(
        future: GetVersionPlugin.version,
        builder: (c, snapshot) {
          if (!snapshot.hasData) {
            return Container();
          }
          return Text(snapshot.data);
        },
      ),
    );
  }
}

后记

本章简单使用了一下 go-flutter 项目, 仓库地址: https://github.com/CaiJingLong/flutter-go-example

这里需要注意下, 由于 go 插件的原因,直接 clone 是跑不起来的, 你需要配置 go 以后, 把 go 插件复制到$GOPATH/src/github.com/caijinglong/go-flutter-plugin/version目录内

后面有时间补测下打包产物

以上