[译]使用Fuse创建原生跨平台APP


date: 2017-07-27 19:59
status: public

title: [译]使用Fuse创建原生跨平台APP

原文链接: How To Create Native Cross-Platform Apps With Fuse

翻译链接:leedoo

概述:Fuse可用于创建应用跑在iOS和Android设备上的工具。你可以很轻松的通过UX Markup - 一种基于XML的语言,但是不同于React NativeNativeScript的组件,Fuse不仅仅可以使用它用于描述UI和Layout布局,你可以使用它去增加动画效果。

Syle样式可以通过以属性的形式,按照驼峰写法如Color,Margin添加到不同的元素上。业务逻辑则可以直接通过Javascript实现。下边我们会一步步实现如何通过这些组件组合创建一个真正的原生app。

这篇文章中,我们将学习到

  • Fuse是谁?Fuse从哪里来,终究要去往哪里

  • Fuse和它的伙伴区别:React Native & NativeScript

  • 创建你人生第一个Fuse App 应用:一个基于地理位置显示天气的小应用。Have a look:

    Weather app Fuse preview

在创建APP中,我们将要学习到:

  • 使用Fuse创建UI组件
  • 如何获取原生设备上的地理位置
  • 最终会对Fuse的优缺点做一个总结

How Does Fuse Work?

额,Fuse如何工作,上图:

How Fuse works

  • 顶层是UX MarkupJavascript,如何使用Fuse,重点就是在说这部分。
  • 中间层是一些Fuse提供的类库,这里包含了一些API,可用于调取原生设备的属性如地理位置,摄像头等
  • 底部Uno compiler:作为默默无闻的底层,这是讲UX Markip转换成存原生代码.app运行时,我们的所有UI层将转为原生UI服务于各自平台。JS代码会在另外一个线程执行(大抵是虚拟机的概念),这样一来,我们JS代码将不会影响UI层的性能

How Does It Compare To React Native And NativeScript?

在我们使用Fuse创建app前,一个重要的问题来了:为什么还需要造一套轮子,呸呸呸,作者的意思是:和其他类工具比较,如何实现同样的效果😅

下边我们将通过比较以下几部分:

  • UI markup
  • Layout
  • JavaScript APIs
  • Extendability
  • JavaScript Libraries
  • Animation
  • Community
  • Development Workflow
  • Debugging

UI markup

在所有的平台中,UI层都是建立在XML语言基础之上。这样,普通的UI组件,比如text fields文本框,switch开关,slider轮播图可以在多平台使用。

React Native 拥有最多的组件,尽管一些组件并不统一。这意味着可以最大限度的使用组件时,一套组件在多平台使用,一套可以仅在特殊平台使用。一小部分组件,例如ProgressBar,在各个平台表现不太一致,意味着:它并非完全是一套代码处处使用(write once, run everywhere)

摊开手来,我们还有一个叫NativeScript的小伙伴,它实现了统一的组件在不同平台上。每一套组件,都有等效的组件在iOS和安卓平台。

Fuse则提供了一部分组件可以满足大部分项目的使用。包含了Video组件——用于播放本地视频或互联网上的,这些还没有在React Native 或NativeScript中实现。当然,Fuse当前也缺失了常用到的date-picker日期组件。

Layout

React Native中,layout布局中使用了Flexbox,所以可以很容易的实现需要的布局。举个栗子,可以通过设置flex:1,和方向flexDirection:row,实现一个容器中的元素等分现有空间,并垂直排布:

<View style={{flex: 1, flexDirection: 'row'}}>
    <View style={{backgroundColor: 'powderblue'}} />
    <View style={{backgroundColor: 'skyblue'}} />
    <View style={{backgroundColor: 'steelblue'}} />
</View>

NativeScript中,布局也需要通过一个容器,基本的布局是StackLayout - 将所有的元素置顶,下边的例子,我们实现一个水平分布:

<StackLayout orientation="vertical">
    <Image src="assets/images/dog.png" />
    <Image src="assets/images/cat.png" />
    <Image src="assets/images/gorilla.png" />
</StackLayout>  

类似地,Fuse是通过容器进行组合,基本的布局使用了语义化的StackPanel,Grid,DockPanel.其中,StackPanel类似NativeScript的StackLayout,下边是例子:

<StackPanel Orientation="Vertical">
    <Panel Height="100" Background="Red" />
    <Panel Height="100" Background="White" />
    <Panel Height="100" Background="Blue" />
</StackPanel>

JAVASCRIPT APIS

所有的平台都覆盖了Javascript API接口,如Camera摄像头调取,platform平台信息,Geolocation地理位置信息,消息推送,HTTP请求和本地存储。然而,从文档来看,React Native无疑是最好,拥有最多的Javascript API弥补了原生和Javacript native间的鸿沟,当然Javacript native并非官方说法,之所以这么说,是他们三方都是通过使用Javascript完成多平台类原生表现。

EXTENDABILITY

开发中需要特殊的设备属性,而Javascript API没有提供,目前各个平台均提供了方法方便开发者直接使用安卓和IOS平台原生的API

NativeScript通过Javascript提供所了所有使用原生API的权限,这意味着你不用接触Swift,OC,或者Java代码去使用原生API,仅仅需要你知道原生API如何使用。

React Native在调用原生API稍显不足,因为不得不使用原生语言实现某些功能。这需要创建一个原生包,暴露一个你需要的公共方法给Javascript,然后通过import引入你的项目。

Fuse允许你扩展功能通过Uno language调用相关属性实现.Uno language是Fuse的核心。

JAVASCRIPT LIBRARIES

React Native 和 NativeScript都支持npm packages,所以使用上你仅需npm install {package-name},按照正常的JS项目开发即可。

Fuse此方面就显得不足,大部分的Js库都没法使用,官方仅仅罗列了一些目前支持的npm包,好消息是Fuse开发者在不断发补丁提供兼容性。

ANIMATION

React Native提供了动画API,可以自定义动画,但是对新手来说并不够友好,即便简单的动画也需要提供不少的代码量,好消息是React Native Animatable让这一切变得很简单,这里是一个fadeIn动画例子:

<Animatable.View animation="fadeIn">Fade me in!</Animatable.View>

NativeScript的动画可以通过CSS3 动画API或者Javascript API实现,这里是一个简单的例子实现scale

.el {
    animation-name: scale;
    animation-duration: 1;
}

@keyframes scale {
    from { transform: scale(1, 1); }
    to { transform: scale(1.5, 1.5); }
}

等同于javascript的实现:

var view = page.getViewById('box'); //must have an element with an ID of box in the markup
view.animate({
    scale: { x: 1.5, y: 1.5},
    duration: 1000
});

Fuse中的动画需要trigger触发,举个例子:当我们按下时,触发某个动画:

<Rectangle Width="50" Height="50" Fill="#ccc">
    <WhilePressed>
        <Scale Factor="2" />
    </WhilePressed>
</Rectangle>

这个例子中,WhilePressed是trigger,<Scale>是动画

COMMUNITY

React Native无疑是王者,拥有facebook大公司背书,从React到React Native,很多组件可以实现复用,使用人数巨多,遇到麻烦尽管提问StackOverflow.同事代码开源在Github,促使React发展飞快。

NativeScript由Telerik创建。目前在github有过万的Star,所以使用起来问题也不大。

Fuse就少有人知道了,因为他不是开源的,又没有大公司在背书。仅仅是通过自己的文档,论坛及代码片段,视频教程推广。不过很快,开发者会开源此项目。

DEVELOPMENT WORKFLOW

暂略

Creating A Weather App With Fuse

现在我们准备创建一个天气类app.他会用到地理位置,然后通过OpenWeatherMap提供的API反馈使用者目前的天气,并显示出来。你可以点击这里查看源码:Github repository

开始前,在OpenWeatherMap网站注册个人账号,通过提供的API key,你可以发送请求使用他提供的API,以备后边用到。

下一步,查看Fuse download page,输入个人邮箱,下载系统适配的Fuse installer安装。安装后启动,点击创建New Project.选择项目路径和项目名称。

Creating a new Fuse project

官方推荐使用Sublime Text 3,并且提供了插件,可提供了代码补全的功能。

安装好插件后,打开MainView.ux文件,这是项目的主要开发文件。

当你通过Fuse创建项目后,常常是从<App>标签开始,这是提示Fuse你创建了一个新页面:

<App>
</App>

Fuse可以使用字体图标,这里我们用到了一些天气图标,使用<Font>标签,通过File属性指定字体图标文件的路径.在这个项目中,字体图标文件再根目录中。同时,我们需要指定一个ux:Global属性,可认为是它的ID,这样我们就可以在其他地方使用这个字体文件。

<Font File="fonts/weather-icons/font/weathericons-regular-webfont.ttf" ux:Global="wi" />

下边是写入Javascript代码,可以通过使用<Javascript>标签,在标签内写入可执行的代码:

<JavaScript>
</JavaScript>

在JS代码中,需要引入Fuse内建的两个库:Observable 和 GeoLocation.

  • Observable允许使用数据绑定,这样当JS改变变量值时,可以自动刷新app的UI层.数据绑定是双向的。使用如下:

    var Observable = require('FuseJS/Observable');    
  • GeoLocation允许你获取使用者设备的地理位置信息:

    var Geolocation = require('FuseJS/GeoLocation');

创建一个对象包含天气图标的十六进制形式,以备后续使用,完整的hex code可以查看链接 GitHub page of the icon font

var icons = {
   'clear': '\uF00d',
   'clouds': '\uF002',
   'drizzle': '\uF009',
   'rain': '\uF008',
   'thunderstorm': '\uF010',
   'snow': '\uF00a',
   'mist': '\uF0b6',
   'fog': '\uF003',
   'temp': '\uF055'
};  

下边写个基本的Kelvin开氏度转Celsius摄氏度的函数,之所以这么做是因为OpenWeatherMap API仅仅为我们提供了开氏温度的姓氏:

function kelvinToCelsius(kelvin){
    return kelvin - 273.15;
}   

下边我们有个小花样,白天时将背景设置为热烈的orange,晚上就改成粉红少女系purple,继续码之:

var hour = (new Date()).getHours();
var color = '#7417C0';
if(hour >= 5 && hour <= 18){
    color = '#f38844';
}   

下边是时候亮出OpenWeather Map API给到你的这个世界对无二的Key了,再创建一个可用于监听的天气数据的变量:

var api_key = 'YOUR OPENWEATHERMAP API KEY';
var weather_data = Observable();

获取地理位置:

var loc = Geolocation.location; 

这里会获取到一个包含有纬度latitude,经度longitude,以及精度accuracy的地址位置信息。but,安卓设备获取位置信息有点问题,当你的设备地址位置设置为不可用时,它不会提供你去打开,所以,为了工作,请为此app开启地址位置权限。

请求OpenWeatherMap API我们使用fetch(),它可以在fuse中随意使用,且不需要什么依赖。它的运行方式同现代浏览器支持的fetch方式一样,可以使用then()的方式写异步流。需要注意一点,返回的数据,需要通过调用json方法才能能来使用:

var req_url = 'http://api.openweathermap.org/data/2.5/weather?lat=' + loc.latitude + '&lon=' + loc.longitude + '&apikey=' + api_key;
fetch(req_url)
.then(function(response) {
    return response.json();
})
.then(function(responseObject) {
    weather_data.value = {
        name: responseObject.name,
        icon: icons[responseObject.weather[0].main.toLowerCase()],
        weather: responseObject.weather[0],
        temperature: kelvinToCelsius(responseObject.main.temp)  + ' °C'
    };
}); 

为了能让你专注读这篇文章,附上正常请求返回的数据形式:

{
   "coord":{
      "lon":120.98,
      "lat":14.6
   },
   "weather":[
      {
         "id":803,
         "main":"Clouds",
         "description":"broken clouds",
         "icon":"04d"
      }
   ],
   "base":"stations",
   "main":{
      "temp":304.15,
      "pressure":1009,
      "humidity":74,
      "temp_min":304.15,
      "temp_max":304.15
   },
   "visibility":10000,
   "wind":{
      "speed":7.2,
      "deg":260
   },
   "clouds":{
      "all":75
   },
   "dt":1473051600,
   "sys":{
      "type":1,
      "id":7706,
      "message":0.0115,
      "country":"PH",
      "sunrise":1473025458,
      "sunset":1473069890
   },
   "id":1701668,
   "name":"Manila",
   "cod":200
}   

现在我们可以把这些数据抛出供UI使用:

module.exports = {
    weather_data: weather_data,
    icons: icons,
    color: color
};  

因为我们仅仅只是一个小项目,所以这里将所有代码写在一个文件中。但是对于项目,推荐将js代码和UX Markup分离开(作者推荐的理由是:UX是设计师来写,JS交割前端开发来做)。正常项目可以将js代码分离,在markup文件中引用:

<JavaScript File="js/weather.js">   

现在可以添加app的UI层了。我们使用<DockPanel>包裹所有元素。默认情况<DockPanel>有一个Dock属性设置为Fill,所有它正是我们理想的容器:可以撑满整个设备屏幕。下边,我们手动添加一个Color属性,用于实现我们的背景色切换:

<DockPanel Color="{color}">
</DockPanel>

DockPanel中,我们使用StatusBarBackground来实现顶部状态栏。如果我们不添加会怎样?如同超级无敌手电筒,你不加自然不会显示状态栏啦。当然,提供这个结构,是方便我们自定义,这里,我们可以省省了:

<StatusBarBackground Dock="Top" />  

下边是内容区域。我们添加<ScrollView>,一个可以在内容超出屏幕时支持上下滑动的组件,里边是StackPanel,包含了我们希望的位置,天气图标,天气描述和当前温度。显示这些变量,我们使用花括号即可:

<ScrollView>
    <StackPanel Alignment="Center">
        <Text Value="{weather_data.name }" FontSize="30" Margin="0,20,0,0" Alignment="Center" TextColor="#fff" />
        <Text Value="{weather_data.icon}" Alignment="Center" Font="wi" FontSize="150" TextColor="#fff" />
        <Text Value="{weather_data.weather.description}" FontSize="30" Alignment="Center" TextColor="#fff" />
        <StackPanel Orientation="Horizontal" Alignment="Center">
            <Text Value="{icons.temp}" Font="wi" FontSize="20" TextColor="#fff" />
            <Text Value="{weather_data.temperature}" Margin="10,0,0,0" FontSize="20" TextColor="#fff" />
        </StackPanel>
    </StackPanel>
</ScrollView>   

现在是不是发现这些属性都是大驼峰写法,不过你一点也不惊艳,因为react也类似。同时,你注意到一些属性如Alignment="Center"反复重复,这是因为Fuse不支持嵌套使用,所以每一个组件需要单独设置。What,这意味着我们要一遍又一遍的写这些样式啊!(作者给出的建议:创建成组件,尽可能重复使用组件,但它确实不怎么方便)

最后一件事,打开根目录下的{your project name}.unoproj文件,这是项目文件,默认他包含这些:

{
  "RootNamespace":"",
  "Packages": [
    "Fuse",
    "FuseJS"
  ],
  "Includes": [
    "*"
  ]
}   

这个文件是配置哪些需要build阶段写入的,默认包含了Fuse,FuseJS包以及所有的项目文件。如果不想打包所有文件,可以做下修改:

"Includes": [
    "*.ux",
    "js/*.js"
]   

你还可以排除一些文件:

"Excludes": [
    "node_modules/"
]   

为了保持轻量,Fuse构建时候仅仅包含了一些最基本的核心,所以我们项目中庸的地理位置信息,需要单独作为包写入:

"Packages": [
    "Fuse",
    "FuseJS",
    "Fuse.GeoLocation"
],  

现在,你可以进行编译执行了。

RUNNING THE APP

通过下载的可视化工具,点击preview预览:

Running the app

这里我们可以预览在Android,iOS或者本地的效果。但是我们的Local回事一个空白页,因为我们拿不到地址位置信息,所以我们可以修改下req_url,只为能在本地查看到效果:

ar req_url = 'http://api.openweathermap.org/data/2.5/weather?q=london,uk&apikey=' + api_key;

现在你可以预览了,如果需要一个真机环境,可以查看官方文档Preview and Export, 查询如何发布。

Pros And Cons Of Fuse

现在我们已经试水了,是时候看看当我们将Fuse作为潜在的开发工具的优缺点了。这里我们只简述一下:

  • 对设计和开发友好
  • 专业于协同和生产
  • 可扩展
  • 类原生的表现

在选择Fuse之前,我们需要关注几点:

  • 结构和样式混为一体
  • Linux不支持,至少目前不在官方考虑计划内
  • 目前还属于测试版,比较粗糙
  • 目前还未开源,正在准备开源中

Final Thoughts

我们已经学习了Fuse,一个混合开发世界中的新军。至少从目前来看,这个项目还是存在很多可能性。在支持多平台和动画表现上可圈可点。事实上,跨平台开发中难得的对设计开发友善。

作者提供更多资料

  • Fuse documentation

    There’s no better place to learn about a new technology than the official documentation.

  • Learning Fuse,” YouTube

    If you learn better through videos, the Fuse team has put together this YouTube playlist to help you learn about the features that Fuse offers.

  • How Fuse Differs From React Native and NativeScript,” Remi Pedersen, Medium

    Learn the technical differences between Fuse and React Native and NativeScript.

附注相关网站:

@2017-07-27 20:00