Fuse 这个技术令人兴奋,不仅可以用来做交互原型,还可以导出成 iOS 和 Android 的原生 App 供生产使用,重点的重点在于输出的成品品质不错,所以私以为值得学习一下 (官方也表示以后会支持导出 Mac,Linux,Windows App)。

我们来通过一张图了解一下 Fuse 做了什么。


所以 Fuse 是一种和 Qt 类似的技术,通过一个中间层封装系统的 API 来实现 “一次编写,到处调试” 的伟大理想。

但是没有了 Qt 的历史包袱 Fuse 要显得更加轻量(就像 Photoshop 之于 Sketch)并且使用 JavaScript 这种 “人人都会” 的语言,从而极大的降低了学习成本,而且 Fuse 在 UI 层是实实在在用的系统级的,不像 Qt 是模拟的 UI,所以最终的成品效果都要好不少。

但是此类技术不敢用于复杂的 App,考虑再三之后,突然想起我之前给 Producter 设计过一个 UI,不如就从此处开始,于是花了 2.5 天的时间完成了这个 App,下面是最终的成品截图,过一段时间再整理完善后会开源出来。

创建 App

Fuse 下载安装后,非常简单就可以创建你的第一个 App —— 点击 New!

如果你的编辑器是 Atom 的话,请务必安装官方的插件。

创建之后目录极为简单,只有两个文件

MainView.ux 就是 App 界面开始的地方。你可以通过在编辑器里右键点击 Preview Local 来运行 App 更酷的是,Fuse 支持实时修改,所以你不需要反复的编辑,编译你的 App。

编译完成后你就得到了你第一个 Fuse App! 当当当~

构建界面

Fuse 的界面和 Qt 的 QML 很类似,每一个元素都实际对应一个 Uno 类,所以你可以随时查阅 Fuse Class Reference 来看看元素都有哪些属性。

通过下面这段代码,就可以给界面添加一段文字

<App Theme="Basic">
  <Text Value="Hi"/>
</App>

Fuse 有个很高端的界面布局系统,你可以通过 Layout 章节进行深入了解,我就不再赘述,先跳过具体的细节。

绑定数据

我们现在给 Fuse 增加一个 JavaScript 控制脚本,首先在项目根目录创建一个 js 文件,就叫做 main.js 吧,然后给 MainView.ux 增加一段标记。

<App Theme="Basic">
  <JavaScript File="main.js" />
  <Text Value="Hi"/>
</App>

假设我们希望绑定 Text 的 Value 到一个变量 name,先修改 Text 的标记

<App Theme="Basic">
  <JavaScript File="main.js" />
  <Text Value="{name}"/>
</App>

接着在 main.js 里编写如下内容

var Observable = require('FuseJS/Observable');

var name = Observable("Kevin");

module.exports = {
	name: name
};

保存之后你就会看到界面上的文字变成了 Kevin,那么绑定如何体现呢? 我们来加一段 Timeout

var Observable = require('FuseJS/Observable');

var name = Observable("Kevin");

setTimeout(function(){
	name.value = "Fuse";
},1000);

module.exports = {
	name: name
};

这时候你保存下,就会看到 1 秒过后文字变成了 Fuse!

列表

根据数据生成列表内容这件事,在 Fuse 里更是深得这几年前端的精髓,你只需要把数据绑定好并写好模版就可以了。

例如我生成十个单身的男子

var people = Observable();

for (var i = 0; i < 10; i++) {
	people.add({name:"Man" + i, age: (2*i)+1})
}

那么要显示这个列表,我们就需要这样来修改 MainView.ux

<App Theme="Basic">
  <JavaScript File="main.js" />

  <ScrollView>
    <StackPanel>
      <Each Items="{people}">
          <Text Value="{name}"/>
          <Text Value="{age}"/>
      </Each>
    </StackPanel>
  </ScrollView>

</App>

ScrollView 提供滚动支持,StackPanel 可以按行排列内容。

于是,我们的效果就实现了!

定义界面 UI

不过这样太难看了点,我们可以美化一下

<App Theme="Basic">
  <JavaScript File="main.js" />
  <ScrollView Padding="30">
    <StackPanel ItemSpacing="20">
      <Each Items="{people}">
          <Rectangle>
            <Stroke Width="2" Brush="#000" />
            <StackPanel ItemSpacing="5" Margin="10">
              <Text Value="{name}" FontSize="20" TextColor="#000" Margin="0"/>
              <Text Value="{age}" TextColor="#aaa"/>
            </StackPanel>
          </Rectangle>
      </Each>
    </StackPanel>
  </ScrollView>
</App>

这是我近期很喜欢的设计风格 :)

加入动画

我希望在按住某个 Cell 的时候可以缩放一下

可以加入 WhilePressed 代码,将其嵌套到你希望触发的控件标签里面即可。

<App Theme="Basic">
  <JavaScript File="main.js" />
  <ScrollView Padding="30">
    <StackPanel ItemSpacing="20">
      <Each Items="{people}">
          <Rectangle>
            <Stroke Width="2" Brush="#000" />
            <StackPanel ItemSpacing="5" Margin="10">
              <Text Value="{name}" FontSize="20" TextColor="#000" Margin="0"/>
              <Text Value="{age}" TextColor="#aaa"/>
            </StackPanel>

            <WhilePressed>
              <Scale Factor="0.98" Duration="0.1"/>
            </WhilePressed>

          </Rectangle>
      </Each>
    </StackPanel>
  </ScrollView>
</App>

从网络获取数据

利用 fetch 方法我们可以直接获取网络数据

fetch("https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://www.digg.com/rss/index.xml")
.then(function(response) {
	status = response.status;
	//Network status
	return response.json();
 })
.then(function(responseObject) {
	//JSON
}).catch(function(err) {
    // An error occured parsing Json
    console.log("Fetch Error" + err);
});

页面导航

如果你写过 iOS 或者其他 App,那么可能对这个需求会很 “旺盛”,在 Fuse 中通过 HierarchicalNavigation 可以定义简单的导航,下面是一个简单的导航结构。

<App Theme="Basic">
<Panel>
    <HierarchicalNavigation ux:Name="nav" ReuseExistingNode="false" Active="mainPage" />

    <Style>
        <Page>
            <EnteringAnimation>
                <Move X="1" RelativeTo="ParentSize" />
            </EnteringAnimation>
            <ExitingAnimation>
                <Move X="-1" RelativeTo="ParentSize" />
            </ExitingAnimation>
        </Page>
    </Style>

    <Page ux:Name="mainPage">
        <StackPanel>
            <Button Text="Page 1">
                <Clicked>
                    <NavigateTo Target="subPage1" />
                </Clicked>
            </Button>
            <Button Text="Page 2">
                <Clicked>
                    <NavigateTo Target="subPage2" />
                </Clicked>
            </Button>
            <WhileCanGoForward>
                <Button Text="Go Forward">
                    <Clicked>
                        <GoForward />
                    </Clicked>
                </Button>
            </WhileCanGoForward>
        </StackPanel>
    </Page>

    <Page ux:Name="subPage1">
        <StackPanel>
            <Text>Welcome to page 1!</Text>
            <Button Text="Go Back">
                <Clicked>
                    <GoBack />
                </Clicked>
            </Button>
        </StackPanel>
    </Page>

    <Page ux:Name="subPage2">
        <StackPanel>
            <Text>Welcome to page 2!</Text>
            <Button Text="Go Back">
                <Clicked>
                    <GoBack />
                </Clicked>
            </Button>
        </StackPanel>
    </Page>
</Panel>
</App>

顺便你也可以发现如何使用事件,例如 Clicked 事件直接加到希望监听的控件下面即可

<Clicked>
  <NavigateTo Target="subPage2" />
</Clicked>

NavigateTo 需要配合 HierarchicalNavigation 一起用,ux:Name 可以给某个控件命名,有了名字就可以互相 “提及” 了

真机运行

在根目录执行一句命令即可, target 也可以换成 Android

uno build --target=iOS --run   

15 分钟完毕,感谢观看,如果你希望深入了解 Fuse,那么就可以通过学习资源的两个链接来搞定!更多问题,则可以去社区提问或者加入他们的官方 Slack 群。

也欢迎通过各种方式与我交流!

学习资源

Learn Fuse

Examples

社区

Fuse Slack

YouTube 官方教程