博客

Google I/O 发布Dart 2.3新特性之code-as-ui

code-as-ui 此特性为 Flutter 开发量身定制。顾名思义,就是让代码的结构和UI的结构保持一致。如图一是官方示例,Column布局下,某些组件需在一定条件下显示,一般有两种写法,一是如官方例子,新增一个方法,里面通过逻辑判断决定显示与否;二是用三目运算符,两种方式都有应用场景

code-as-ui 此特性为 Flutter 开发量身定制。顾名思义,就是让代码的结构和UI的结构保持一致。如图一是官方示例,Column布局下,某些组件需在一定条件下显示,一般有两种写法,一是如官方例子,新增一个方法,里面通过逻辑判断决定显示与否;二是用三目运算符,两种方式都有应用场景。图一右边的例子是支持在数组字面量[]中的条件判断和数组合并(…),其结构和UI结构一致,清晰明了。

图二和图三是截取自《蒲说》项目的真实片段,学习新特性后,立即优化了代码,代码简洁和更清晰。

图二
图三

Google I/O ’19: Building more helpful Google for everyone

每年看到Google Keynote都发出同样的感叹:Google真是一家服务公司。众所周知,Google技术实力雄厚,在很多领域处于领先地位,且对技术的追求从未止步。但它并非一般的技术公司,沉溺于技术的研究,而忽略产品落地和用户体验。

Google将前沿技术落地成了产品或者提升产品体验,比如去年推出的对某些搜索内容(事件)自动聚合,形成时间线,方便看到事件前因后果;提升Google 翻译质量,文字和语音都可实时翻译;Google Len 集合了图形图像处理,文本处理,语义分析等技术,拿起相机对准事物,就会自动搜索出关联信息;

理念分析

more helpful 是其目标,不强调技术多牛叉,只是一心为用户创造更有用的功能;everyone 是其服务对象,不局限于专业技术人员,而是所有人,包括为残障人士。

亮点

印象最深刻的是:On Device。将众多以前需要联网的功能,放到了本机离线使用,比如视频实时生成字幕,翻译等。

Flutter 2 种导航(路由)方式介绍

不浪费大家时间阅读全文,还是提到开始,帮助大家抓重点阅读。

  1. push和pushNamed无参数导航
  2. push和pushNamed含参数导航
  3. 404路由自定义
  4. git 克隆指定分支和切换分支

正文

路由的概念很广泛,其主要功能是约定一套到达目的的映射规则,方便使用。本场景中是定义一个字符串映射到某个界面。

Flutter 有两种路由形式实现导航,

  • Navigator.of(context).pushNamed(routeName, arguments),
  • Navigator.of(context).push(route);

及其衍生的形式,常见的如导航到某个页面后要删除导航路径中的某些页面。如果是导航到无参数的页面,两种形式实现都很直接;如果是有参数的页面,使用 pushNamed 会稍微绕一点,下面说明下两种方式的使用方式。

Starter 启动工程

开始之前,先创建2个界面,分别是

  • WithParameterPage,接收参数的页面
  • WithoutParameterPage,不接收参数的页面

加上首页,一共3个界面,可通过 git 下载到本地

git clone https://github.com/juwencheng/flutter_route_demo.git -b starter

导航到无参页面

push方式

push 方法接收的参数是 Route<T> 类型的对象,通常我们使用系统提供的 MaterialPageRoute,它提供跨平台的导航动画效果,安卓是从下往上的模态动画,苹果是从右往左的水平切换动画,下面是官网对 MaterialPageRoute 的介绍

A modal route that replaces the entire screen with a platform-adaptive transition.
For Android, the entrance transition for the page slides the page upwards and fades it in. The exit transition is the same, but in reverse.
The transition is adaptive to the platform and on iOS, the page slides in from the right and exits in reverse. The page also shifts to the left in parallax when another page enters to cover it. (These directions are flipped in environments with a right-to-left reading direction.)
By default, when a modal route is replaced by another, the previous route remains in memory. To free all the resources when this is not necessary, set maintainState to false.
The fullscreenDialog property specifies whether the incoming page is a fullscreen modal dialog. On iOS, those pages animate from the bottom to the top rather than horizontally.
The type T specifies the return type of the route which can be supplied as the route is popped from the stack via Navigator.pop by providing the optional result argument.

https://docs.flutter.io/flutter/material/MaterialPageRoute-class.html

实现代码如下

Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WithoutParameterPage(),
),
);

pushNamed 方式

pushNamed 方法接收两个参数,其中 String 类型的 routeName 为必需,Object 类型命名参数 arguments 为可选参数,下面节选自官网

Push a named route onto the navigator that most tightly encloses the given context.

https://docs.flutter.io/flutter/widgets/Navigator/pushNamed.html

代码实现如下

Navigator.of(context).pushNamed(
'without_parameter_page',
);

可以看到形式上简洁很多。不过还没完,需要在 main.dart 的 MaterialApp 对象中添加 routes 对象定义 String 到 页面的映射规则,代码如下

...
routes: {
'/without_parameter_page': (context) => WithoutParameterPage(),
},
...

单纯的字符串并不容易记忆,一旦拼写错误,就无法导航到预期的界面,因此通常会在 StatefulWidget 或 StatelessWidget 中定义静态属性 routeName

class WithoutParameterPage extends StatefulWidget {
static constString routeName = "/without_parameter_page";
@override
_WithoutParameterPageState createState() => _WithoutParameterPageState();
}

修改 main.dart 中的定义

routes: {
WithoutParameterPage.routeName: (context) => WithoutParameterPage(),
},

及使用

Navigator.of(context).pushNamed(
WithoutParameterPage.routeName,
);

总结起来使用 pushNamed 方式比 push 方式多一个定义的过程。

完整代码可切换分支到 without_parameter 查看

git checkout without_parameter

如果没有下载,可通过如下命令直接克隆 without_parameter 分支

git clone https://github.com/juwencheng/flutter_route_demo.git -b without_parameter

导航到含参页面

看完上面的介绍,兴致勃勃的你已经迫不及待想自己尝试下如何导航到含参页面了。过程大致是

  • 打开 WithParameterPage,发现已经定义了 parameters,直接使用
  • 使用 push 方式导航时,把 WithoutParameterPage 修改为 WithParameterPage,并传入参数 {“hello”: “world”},运行,啪,导航过去了,并在界面中央出现了 {“hello”: “world”},完美。
  • 使用 pushNamed方式导航时,
    • 在 WithParameterPage 中定义 routeName
    • 在 main.dart 中定义路由规则 WithParameterPage.routeName: (context) => WithParameterPage()
    • 调用 pushNamed 方法时,第二个参数用来接收参数的,继续将 {“hello”: “world”} 传入,运行,准备见证奇迹的时候,发现界面中并未接收到参数,是不是有缓存,再运行一次,还是不行。

本以为可以顺利实现,结果却令人疑惑。回到方法本身,思考个问题,传递的参数,是怎么绑定到界面的呢?文档中没有提及反射,自动绑定或者约定一种命名方式让程序可以处理。再阅读官方文档

The route name will be passed to that navigator’s onGenerateRoute callback. The returned route will be pushed into the navigator.
The new route and the previous route (if any) are notified (see Route.didPush and Route.didChangeNext). If the Navigator has any Navigator.observers, they will be notified as well (seeNavigatorObserver.didPush).
Ongoing gestures within the current route are canceled when a new route is pushed.
Returns a Future that completes to the result value passed to pop when the pushed route is popped off the navigator.
The T type argument is the type of the return value of the route. The provided arguments are passed to the pushed route via RouteSettings.arguments. Any object can be passed as arguments (e.g. a Stringint, or an instance of a custom MyRouteArguments class). Often, a Map is used to pass key-value pairs.
The arguments may be used in Navigator.onGenerateRoute or Navigator.onUnknownRoute to construct the route.

https://docs.flutter.io/flutter/widgets/Navigator/pushNamed.html

末尾发现了一点端倪,提刀了 onGenerateRoute 方法。就在 MaterialApp 试试有没有此方法,正好发现方法,着手改动一番。(如果没那么巧,1. 顺着文档继续挖下去,看能够找到切入点;2. 谷歌查询关键字,搜索别人的使用方法)

onGenerateRoute: (settings) {
print(settings);
return MaterialPageRoute(builder: (context) => WithParameterPage());
},

在 MaterialApp 中添加以上代码后,运行,期待打印出 settings 信息,尝试多次后发现并没有。有点小失望?继续往下看

直接公布答案,在 routes 中定义 ‘/with_parameter_page’ 规则后,系统可以自动处理该路由,因此不会进入 onGenerateRoute,需将 routes 中的定义删除

routes: {
WithoutParameterPage.routeName: (context) => WithoutParameterPage(),
// WithParameterPage.routeName: (context) => WithParameterPage(),
},

接下来完善 onGenerateRoute 代码

switch (settings.name) {
case WithParameterPage.routeName:
return MaterialPageRoute(
builder: (context) =>
WithParameterPage(parameters: settings.arguments));
default:
break;
}

至此,两种路由方式介绍完毕。最后还想提一下 onUnknownRoute 方法,在无法确保路由写错的情况下,可以通过 onUnknownRoute 指定一个默认的 404 界面来暴露问题,方便查找(如果不暴露问题,界面会吞掉错误,不会有响应,需查看控制台发现问题)。代码如下

onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundPage(settings: settings),
);
},

NotFoundPage 的内容可切换到 with_parameter 分支查看,切换方法见上小节。

文中代码格式化效果不佳,正想办法解决,如果有好的建议可提供。

如果遇到无法运行或者通过文中代码无法达到预期效果等问题,欢迎在github上提交issue。

Flutter 开发体验

去年关注 Flutter 时,处于beta1版本,截至目前已经发布1.2正式版本,社区很活跃,发展很迅速。起初吸引我的有以下几个点:

  • Powered By Google,是谷歌发起的开源跨平台方案,有大公司背书,技术实力有保障,也不会轻易放弃维护。
  • 跨平台解决方案,一套代码完成大部分 Android 和 iOS 的开发工作,包括UI和逻辑代码。
  • 运行效率高(没有量化评估,从体验流畅度主观得到)。
  • Dart 语言上手难度低(也支持高级特性),熟悉 JavaScritp,TypeScript 和 Java 的肯定会有似曾相识的感觉。
  • (安卓开发而言)编写组件不需要 XML 布局文件,纯代码即可完成,方便移植。

学习的第一步是跟着官网搭建环境,把官网的 Demo 运行起来,然后看看官方文档,对照 iOS 写常用到的小组件,一定要非常熟悉系统提供的组件,能够在看到 UI 设计稿的时候能够联想到应该用什么(顺便提一句,我手机上都有官方的 demo 大全,有事儿没事儿就打开看看,加强对各种组件的印象)。同时可以在 YouTube 上找找视频教程,在视频的开头都会展示最终的完成效果,先想一下自己会如何实现。有些教程作的 UI 效果很棒,值得多看并亲自动手实现。慢慢地就会加深了对它的理解,比如以前组件的属性,变成了单独的组件;Widget 并不是真正的视图,类似于 iOS 的 layer对象;没有复杂的生命周期函数。总得来说,学习资料多,学习过程丰富。

后来新项目立项,做技术评估,仔细考量后,决定使用 Flutter 开发,累计2个月时间开发完成,顺利交付。至今,已将 Flutter 应用到实际项目开发中4月有余,可以对大家关心的问题分享下自己的心得体会。

Flutter 能给项目带来哪些好处

1. 降低开发费用

之前需要两人做的工作,现在 1 个经验丰富的开发就可以完成,一套代码能够解决大部分问题,即使小部分问题无法使用 dart 解决,也可通过插件(MethodChannel)方式调用原生代码解决。例如拍照、相册等需要和硬件打交道的功能无法替代,推送、微信分享、微信支付、支付宝支付等三方库目前没有对 dart 做原生支持也无法替代。

2. 开发效率的提升

主要得益于热更新(Hot Reload)技术,可以实现代码实时看到修改效果,无需重新编译打包。使用过 RN 的开发不会陌生,没用过的赶紧体验下,带来的开发体验无与伦比。

3. 降低沟通成本

我尤为重视这点,沟通一直是项目进度的风险点,经历过太多的看似有效实则无效的沟通,当然仅靠新的技术并不能规避和消除这个风险点,需要付出更多的努力才行。这里特指 Flutter 和 Android & iOS 开发模式上的对比。开发功能一般会经过沟通—理解—实施三个阶段和两个过程。单位功能越多人经历这个过程,风险越高,举例来说,同样的功能,和2个人沟通让他们同时实现,相比于和1个人沟通,让他实现出现风险的概率更高。原因在于,2个人对于同样的对话理解程度不同,一旦理解出现偏差,做出来的东西必定会出现隐患;

Flutter 技术相关 Q&A

容易上手吗?

对 Android 开发来说更容易,毕竟是谷歌出品,很多设计元素及设计理念和 Android 相近。对 iOS 开发来说可以对比学习,不理解的概念多看几遍,查查资料,也能很快上手;如果没开发经验,学习 Flutter 应该比 Android 和 iOS 简单,不过建议还是得学习点 Android 和 iOS 基础,至少能够正常使用插件,遇到问题能够知道解决方向。

效率高吗?

我没有做量化测试,印象做有很多人做了相关的测试,可以搜索下。从主观体验上说,效率接近原生,某些场景下会比不上原生,比如 iOS 导航跳转的时候,没有 iOS 丝滑流畅。如果想直观体验可以下载我司正在开发中的App:蒲说,亲自看看有啥区别。之后会开一个专区介绍下我司开发的APP及其功能,敬请期待吧~

有没有遇到什么坑?

这个肯定有,当时做安卓推送的时候就出现能够获取到 DeviceToken ,无法接受到消息的问题,下载官方 Demo 后,再结合文档把他解决了。问题主要是由 Native 的生命周期和 Flutter 生命周期有时差导致的。相信后面还会遇到。

还有,为了保持底部每个 BottomNavigationBar 的状态(滚动位置,数据呈现等),使用了 IndexedStack ,导致需要再界面显示时更新数据功能无法实现,没有机会触发刷新方法(少了 iOS 的 viewWillAppear, viewDidAppear这系列的生命周期方法)。然后使用了 StreamController 来手动触发更新数据。

没有 iOS 的 NotificationCenter,可以广播消息给程序的任意地方。要实现这种功能也依靠了 StreamController

强类型校验,String 转 int 和 double 都得分开写,否则会导致转换异常。目前还没找到很好的解决方法。

全局异常处理,不小心就“全屏乱码”(客户的反馈,很可爱)了。

有没有参考资料可供分享?

当然,下面是我常访问的网站

  • Flutter 官网
  • Material Design 官网,不管是开发还是设计,都可以多访问它,里面定义了 MD 设计规范和建议。访问有点慢或者需要科学上网,可自备工具。
  • YouTube 频道:
  • Medium 关注了几个 Publications ,其他的个人作者就不一一列举,可以通过搜索 Flutter 查看。
    • Flutter
    • Flutter Community,
    • Coding with Flutter

如果还有其他问题,请留言,会上榜哟~~

希望能够对你有所帮助,下篇文章见。

开站语

从公司成立开始,就计划搭建自己的网站,陆陆续续尝试了很多次,没一个满足自己的需求,要么太简单,要么太花哨,不符合一家科技型公司的气质。

近段时间仔细思考着零零总总的时间,找到的问题的根源:自己并没想清要透过官网表达的内容。于是删除思绪,整理思路将最想要的整理出来

  • 展示公司是做什么的;
  • 有哪些成果;
  • 能够让客户联系到我们;
  • 方便发布内容;
  • (主观性)不从众,不花哨,能看出是一家技术公司。

升级WordPress后,被新的编辑系统打动,加上目前有项目运行在WordPress上,对它也比较熟悉,果断就用它了。和之前不同,不去找功能齐全,大部分不会用的主题,而采用官方的主题模版 Twenty Sixteen ,它的优点是界面简洁,中文字体显示效果佳,有联系表单,可以满足我的需要。但有了工具并不代表能做出好的产品,还需要坚持不懈的努力,完善和优化内容。


发布哪些内容?

公司大事件

公司的大事件,如官网发布,新系统上线,取得重大成绩等信息

公司日志

包含公司运营的日志,团队建设,员工人员变动等信息

项目信息

包含公司参与和主导的项目介绍,项目进展等简要信息

技术分享

分享开发中用到的技术,遇到的疑难问题。