抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

写给前端工程师的 Flutter 教程(下)

2019 年 10 月 31 日

写给前端工程师的 Flutter 教程(下)

最爱折腾的就是前端工程师了,从 jQuery 折腾到 AngularJs,再折腾到 Vue、React。最爱跨屏的也是前端工程师,从 phonegap,折腾到 React Native,这不又折腾到了 Flutter。

图啥?低成本地为用户带来更优秀的用户体验。目前来说 Flutter 可能是其中最优秀的一种方案了。


1. Widget 布局

上说过 Flutter 布局思路来自 CSS,而 Flutter 中一切皆 Widget,因此整体布局也很简单:


  • 容器组件 Container

  • decoration 装饰属性,设置背景色,背景图,边框,圆角,阴影和渐变等

  • margin

  • padding

  • alignment

  • width

  • height

  • Padding,Center

  • Row,Column,Flex

  • Wrap, Flow 流式布局

  • stack, z 轴布局

  • ……


Flutter 中 Widget 可以分为三类,形如 React 中“展示组件”、“容器组件”,“context”。


2. StatelessWidget

这个就是 Flutter 中的“展示组件”,自身不保存状态,外部参数变化就销毁重新创建。Flutter 建议尽量使用无状态的组件。


3. StatefulWidget

状态组件就是类似于 React 中的“容器组件”了,Flutter 中状态组件写法会稍微不一样。


class Counter extends StatefulWidget {  // This class is the configuration for the state. It holds the  // values (in this case nothing) provided by the parent and used by the build  // method of the State. Fields in a Widget subclass are always marked "final".
@override _CounterState createState() => _CounterState();}
class _CounterState extends State<Counter> { int _counter = 0;
void _increment() { setState(() { // This call to setState tells the Flutter framework that // something has changed in this State, which causes it to rerun // the build method below so that the display can reflect the // updated values. If you change _counter without calling // setState(), then the build method won't be called again, // and so nothing would appear to happen. _counter++; }); }
@override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance // as done by the _increment method above. // The Flutter framework has been optimized to make rerunning // build methods fast, so that you can just rebuild anything that // needs updating rather than having to individually change // instances of widgets. return Row( children: <Widget>[ RaisedButton( onPressed: _increment, child: Text('Increment'), ), Text('Count: $_counter'), ], ); }}
复制代码


可以看到 Flutter 中直接使用了和 React 中同名的 setState 方法,不过不会有变量合并的东西,当然也有生命周期。



Flutter StatefulWidget 生命周期


可以看到一个有状态的组件需要两个 Class,这样写的原因在于,Flutter 中 Widget 都是 immmutable 的,状态组件的状态保存在 State 中,组件仍然每次重新创建,Widget 在这里只是一种对组件的描述,Flutter 会 diff 转换成 Element,然后转换成 RenderObject 才渲染。



Flutter render object


实际上 Widget 只是作为组件结构一种描述,还可以带来的好处是,你可以更方便的做一些主题性的组件, Flutter 官方提供的 Material Components widgets 和 Cupertino (iOS-style) widgets 质量就相当高,再配合 Flutter 亚秒级的 Hot Reload,开发体验可以说挺不错的。


State Management

setState()可以很方便的管理组件内的数据,但是 Flutter 中状态同样是从上往下流转的,因此也会遇到和 React 中同样的问题,如果组件树太深,逐层状态创建就显得很麻烦了,更不要说代码的易读和易维护性了。


1. InheritedWidget

同样 Flutter 也有个 context 一样的东西,那就是 InheritedWidget,使用起来也很简单。


class GlobalData extends InheritedWidget {  final int count;  GlobalData({Key key, this.count,Widget child}):super(key:key,child:child);
@override bool updateShouldNotify(GlobalData oldWidget) { return oldWidget.count != count; }
static GlobalData of(BuildContext context) => context.inheritFromWidgetOfExactType(GlobalData);}
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;
void _incrementCounter() { _counter++; }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: GlobalData( count: _counter, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), Body(), Body2() ], ), ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); }}
class Body extends StatelessWidget { @override Widget build(BuildContext context) { GlobalData globalData = GlobalData.of(context); return Text(globalData.count.toString()); }}
class Body2 extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build GlobalData globalData = GlobalData.of(context); return Text(globalData.count.toString()); }
复制代码


2. BlOC

BlOC 是 Flutter team 提出建议的另一种更高级的数据组织方式,也是我最中意的方式。简单来说:BlOC= InheritedWidget + RxDart(Stream)


Dart 语言中内置了 Steam,Stream ~= Observable,配合 RxDart, 然后加上 StreamBuilder 会是一种异常强大和自由的模式。


class GlobalData extends InheritedWidget {  final int count;  final Stream<String> timeInterval$ = new Stream.periodic(Duration(seconds: 10)).map((time) => new DateTime.now().toString());  GlobalData({Key key, this.count,Widget child}):super(key:key,child:child);
@override bool updateShouldNotify(GlobalData oldWidget) { return oldWidget.count != count; }
static GlobalData of(BuildContext context) => context.inheritFromWidgetOfExactType(GlobalData);
}
class TimerView extends StatelessWidget {
@override Widget build(BuildContext context) { GlobalData globalData = GlobalData.of(context); return StreamBuilder( stream: globalData.timeInterval$, builder: (context, snapshot) { return Text(snapshot?.data ?? ''); } ); }}
复制代码


当然 Bloc 的问题在于


  • 学习成本略高,Rx 的概念要吃透,不然你会抓狂

  • 自由带来的问题是,可能代码不如 Redux 类的规整。


顺便,今年 Apple 也拥抱了响应式,Combine(Rx like) + SwiftUI 也基本等于 Bloc 了


所以,Rx 还是要赶紧学起来 :grimacing。


除去 Bloc,Flutter 中还是可以使用其他的方案,譬如:


  • Flutter Redux

  • 阿里闲鱼的 Fish Redux,据说性能很好

  • Mobx

  • ……


展开来说现在的前端开发使用强大的框架页面组装已经不是难点了。开发的难点在于如何组合富交互所需的数据,也就是上面图中的 state 部分。


更具体来说,是怎么优雅,高效,易维护地处理短暂数据(ephemeral state)setState()和需要共享的 App State 的问题,这是个工程性的问题,但往往也是日常开发最难的事情了,引用 Redux 作者 Dan 的一句:


“The rule of thumb is:Do whatever is less awkward.”


到这里,主要的部分已经讲完了,有这些已经可以开发出一个不错的 App 了。剩下的就当成一个 bonus 吧。


测试

Flutter debugger,测试都是出场自带,用起来也不难。


// 测试在/test/目录下面void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump();
// Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); });}
复制代码


包管理,资源管理

类似与 JavaScript 的 npm,Flutter,也就是 Dart 也有自己的包仓库。不过项目包的依赖使用 yaml 文件来描述:


name: appdescription: A new Flutter project.version: 1.0.0+1
environment: sdk: ">=2.1.0 <3.0.0"
dependencies: flutter: sdk: flutter
cupertino_icons: ^0.1.2
复制代码


生命周期

移动应用总归需要应用级别的生命周期,flutter 中使用生命周期钩子,也非常的简单:


class MyApp extends StatefulWidget {  @override  _MyAppState createState() => new _MyAppState();}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); }
@override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); }
@override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.inactive: print('AppLifecycleState.inactive'); break; case AppLifecycleState.paused: print('AppLifecycleState.paused'); break; case AppLifecycleState.resumed: print('AppLifecycleState.resumed'); break; case AppLifecycleState.suspending: print('AppLifecycleState.suspending'); break; } super.didChangeAppLifecycleState(state); }
@override Widget build(BuildContext context) { return Container(); }}
复制代码


使用原生能力

和 ReactNative 类似,Flutter 也是使用类似事件的机制来使用平台相关能力。



Flutter platform channels


Flutter Web, Flutter Desktop

这些还在开发当中,鉴于对 Dart 喜欢,以及对 Flutter 性能的乐观,这些倒是很值得期待。



Flutter Web 架构


还记得平台只是给 Flutter 提供一个画布么,Flutter Desktop 未来更是可以大有可为。最后每种方案,每种技术都有优缺点,甚至技术的架构决定了,有些缺陷可能永远都没法改进。


本文转载自公众号云加社区(ID:QcloudCommunity)。


原文链接:


https://mp.weixin.qq.com/s/l__TJKw0DGOh01ePqAS9qw


2019 年 10 月 31 日 18:58650

评论

发布
暂无评论
发现更多内容

10. 比找女朋友还难的技术点,Python 面向对象

梦想橡皮擦

Python 2月春节不断更 python入门

深入 Python 解释器源码,我终于搞明白了字符串驻留的原理!

Python猫

Python 编程

诊所数字化从预约开始

boshi

数字化医疗 七日更 线上预约

日记 2021年2月14日(周日)

Changing Lin

2月春节不断更

【STM32】串口通信出现乱码(使用官方标准库)

AXYZdong

硬件 stm32 2月春节不断更

Idea应用启动时WEB-INF/lib无效标记问题处理

程序员架构进阶

Java IntelliJ IDEA 七日更 2月春节不断更

深入理解gradle中的task

程序那些事

Java maven Gradle 程序那些事 构建工具

机器学习笔记之:

Nydia

JUnit速查手册

jiangling500

Java JUnit

数字资产助力未来十年打赢数字经济战

CECBC区块链专委会

数字经济

第十二周课后作业

Binary

ElasticSearch.01-简介

insight

elasticsearch 2月春节不断更

熬夜7天,我总结了JavaScript与ES的25个重要知识点!

魔王哪吒

学习 程序员 面试 前端 2月春节不断更

Tomcat异常: Unable to process Jar entry [module-info.class] from Jar

小马哥

Java maven 七日更 二月春节不断更

面试官系列:你对Spring事件发布和广播监听有了解吗?

后台技术汇

面试官 2月春节不断更

Elasticsearch dynamic mapping

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

保持模块的兼容性

Rayjun

go go modules

SpringMVC专栏 第1篇 - 快速入门

小马哥

Java spring Spring MVC 七日更 二月春节不断更

【LeetCode】最大连续1的个数Java题解

HQ数字卡

算法 LeetCode 2月春节不断更

今日出门

Nydia

松耦合

sinsy

设计模式 RabbitMQ

日记 2021年2月15日(周一)

Changing Lin

2月春节不断更

《我们脑中挥之不去的问题》 - 卓克科普(3)

石云升

读书笔记 科普 2月春节不断更

记一次有意思的微信视频号直播

小匚

产品经理

第十二周学习总结

Binary

工作学习累了?试试 GitHub 上的那些简单易学的游戏项目吧!

JackTian

GitHub 游戏 开源项目 2月春节不断更

公路交通区块链技术的痛点问题和典型场景应用

CECBC区块链专委会

区块链

中国科学家突破区块链核心技术

CECBC区块链专委会

区块链

华为 MPLS的数据转发流程

艺博东

华为

翻译:《实用的Python编程》01_03_Numbers

codists

Python

【LeetCode】情侣牵手Java题解

HQ数字卡

算法 LeetCode 2月春节不断更

Study Go: From Zero to Hero

Study Go: From Zero to Hero

写给前端工程师的 Flutter 教程(下)-InfoQ