Flutter 和 Dart 概述
本文最后更新于 2021年4月4日 晚上
这篇文章主要讲 Dart 和 Flutter 的一些基础性内容.
Dart 语言
官网: https://www.dartlang.org/
关于开发的 IDE, 目前来看 VS Code 有点太简易了, 换成 Ideal 的社区版先, 只是无法接受 VS Code 在折叠行的时候有时还会出现水平滚动, 对于使用 MAC 触摸板并不友好, 但用鼠标的情况下倒是没什么问题.
下载安装好了, 只需要添加 Flutter 插件即可.
讲解 Dart 的文章除了官方文档, 还有这些:
https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf
在 Dart 中的多线程机制叫做 Isolate, 其实现为不同的 Isolate 不会共享内存, 这样也就不存在共享资源时候加锁了. 在多个 Isolate 间使用消息进行通信.
Dart 和 JS 类似, 是单线程的, 提供 Future API 或 async/await
作异步支持, 详见这个链接.
Dart 的内存回收机制专门针对大量小对象回收进行了优化, 这样在更新 Widget 显示的时候尤其高效.
Dart 2 中的 new
关键字是可选的.
定义函数
1 |
|
Dart 中需要有一个 Main 函数作为入口, 且调用 runApp 来启动整个程序.
一些点
- 有类型推断
- 任何东西都是对象
- 任意类型的定义使用
dynamic
, 类似于 Any 的效果 var
表示变量final
表示常量, 类似let
.- 有泛型
- 有顶层的函数, 也有类方法和对象方法
- 有全局变量或常量, 有类属性或对象属性.
- 没有访问控制关键字, 取代的是命名上的约定, 比如下划线开头的是
private
的.
变量常量定义
1 |
|
变量定义时, 如果没有赋初值, 则默认的初值都是 null
. 万物皆对象, 故 int 的初值也会是 null…
1 |
|
assert
代码会在生产环境下被忽略, 但在开发环境下的作用十分强大.
final 和 const
final
定义的量只能被赋值一次.
const
定义的常量是编译时常量, 即在编译阶段就确定了它的值和类型.
const
隐含了该量是 final
.
final 的顶层常量或类常量属性在第一次使用的时候就会被初始化, 这个机制可以用来生成单例对象.
final
和 swift 中的 let
看来作用是完全相同的.
之前可能会遇到用 const 修饰的字面量, 比如 const [].
内置类型
- numbers
- strings
- booleans
- lists (also known as arrays)
- maps
- runes (for expressing Unicode characters in a string)
- symbols
Both int
and double
are subtypes of num
., 即 int 和 double 都是 num 的子类型.
下面是数值和字符串间的转换:
1 |
|
另外 double 是 64 位的, int 根据平台不同有不同, 最长不超过 64位. 在 int 上可以进行多种位操作:
1 |
|
字符串可以用单引号也可以双引号, 看情况而定, 总地来说仅仅是为了避免转义:
1 |
|
字符串可以使用 ==
来判等, 估计也是重载的操作符?
多行字符串借鉴了 swift 的, 可以使用 '''
, 或者 """
来.
可以检查是否是数值:
1 |
|
这个和 JS 的类似.
list 的话就是 Array:
1 |
|
还可以创建一个常量的 list 赋值给变量:
1 |
|
Map 就是字典:
1 |
|
Symbol: 在常量或变量前加 #
号就可以获取它对应的 symbol
.
函数
- 使用
{}
把参数那一坨括起来的话, 就可以定义有名字的参数, 默认调用时参数是没有名字的:
1 |
|
看懂这个的话就知道为什么这么多大括号在 API 里面了… 擦
默认情况下所有参数都是可忽略的!! 果然是!!!
- 使用
@required
标记参数是必须的!!!
1 |
|
如果用中括号, 则显式表示这些参数是可选的.
可以设置默认参数! 和 swift 一样.
main
函数实际有一个参数表1
2
3
4
5
6
7void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}函数是一等公民, 也就是说可看成是 lambda 表达式的一种表现形式而已.
1
2
3
4
5
6
7
8
9
10void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';在 Dart 中写 Closure 的语法上有些许差异:
1
2
3
4
5
6
7
8var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
// 还可以像 C# 那样搞成单行的:
list.forEach(
(item) => print('${list.indexOf(item)}: $item'));可以像下面这样返回 Closure: 注意
Function
类型的使用1
2
3Function makeAdder(num addBy) {
return (num i) => addBy + i;
}注意有个坑: 所有的函数都会返回值, 如果没有
return
语句, 则默认返回的是null
, 且会被自动添加到函数末尾, 除非显式标记为void
!!!!!!!!!!
操作符
除了 /
外, 如果对 double 使用 ~/
, 则是整除的结果, 这样比较方便.
使用 ==
判断两个对象的值是否相等, 如果要判断两个对象是不是同一个对象, 使用 identical()
方法来判断.
==
就是操作符重载的效果, 因为还可以在对象上调用它: .==
….
使用 as
进行类型转换, 没有可选类型, 也就没有 as?
了.
使用 is
和 is!
来判断是否是某类型.
Flutter 在 null
上调用对象方法会抛出异常. 故一般需要在转型前进行判断.
使用 ??=
来赋值一次, 如果变量已经有值了, 则不再赋值.
使用 ??
来提供 null 时候的默认值, 和 swift 一致.
可以使用 ?.
来可选访问.
流程控制
仍然是老格调, 需要 break, 如果不加, 默认就是 fallthrough 的.
异常
异常不需要显式写 throws
, 也不是必须 catch 异常. 并且可以抛出普通对象.
捕捉异常的语法是这样的:
1 |
|
使用 on
来捕捉指定类型的异常, 使用 catch
来捕捉 Exception
或其子类. finally
处于链条最后, 执行善后工作, 比如释放资源等.
catch 语句中支持一个或两个参数, 两个参数时如下:
1 |
|
第二个参数代表调用栈.
一个非常好用的东西: rethrow
!!!
如下所示, 使用 rethrow 可以将异常继续传播:
1 |
|
其实貌似 swift 中也有, 只是叫做 rethrows
.
类
所有的 class 都继承自 Object
.
构造方法可以是 类名()
, 也可以是 类名.构造方法名()
:
1 |
|
使用常量构造函数来生成编译时常量:
1 |
|
使用 const 来定义常量, 即可生成一个常量上下文, 这样的话该类型的构造函数内所有的内容都是常量了:
1 |
|
使用 runtimeType
获取某个常量或变量的运行时类型, 返回值是 Type
类型的:
1 |
|
构造方法
构造方法普通写法:
1 |
|
语法糖写法:
1 |
|
另外可以写有名字的构造方法:
1 |
|
父类构造方法的调用类似 C#:
1 |
|
可以实现类似 swift 那种在访问 self 之前先把所有属性都初始化的方式:
1 |
|
注意在冒号右边是无法访问 this
的, 因为此时还没有 this
. 反正就是可以在冒号右边用逗号分隔若干个表达式, 这些表达式类似 swift 在调用 super.init
之前进行的.
调用自身的构造方法, 和 C# 类似:
1 |
|
常量构造方法:
1 |
|
需要保证所有的对象属性都是 final
的.
工厂构造方法(使用 factory
关键字): 一种特殊的构造方法, 它可能不会总生成新的对象:
1 |
|
由于 Dart 是单线程的, 所以也不存在创建单例的时候会出现创建两个的情况.
调用工厂构造方法的时候, 和普通的构造函数调用语法相同:
1 |
|
使用 set
和 get
关键字定义某个域的 setter 和 getter, 这个可以理解为计算属性的写法不同而已:
1 |
|
默认情况下系统会自动为域创建getter 和 setter(如果不是 final 域).
抽象类作接口又回来了!
1 |
|
而且结合 factory
构造方法, 可以在抽象类中实现提供子类对象的工厂方法.
Dart 中没有接口的概念, 但每个类都会隐式生成一个对应的接口, 接口内容就是所有的对象成员(属性和方法), 如果想让一个类实现另外一个类的接口, 而非继承, 则可以这样写:
1 |
|
这样还可以让一个类实现多个接口(感觉好像是多继承, 实际上只是实现接口而已):
1 |
|
继承的话, 使用 extends
关键字即可.
运算符重载的话, 可以这样写:
1 |
|
重写 ==
操作符的时候需要重写 hashCode
属性的 getter
.
重写 noSuchMethod
方法可以提供对未实现方法的调用时候提供的信息.
enum 类型
简单定义:
1 |
|
默认成员的值从 0 开始.
可以通过 values
属性来获取所有 case 的列表.
MixIn
是否和扩展类似呢? 貌似就是和扩展一样, 不过比扩展更强, 因为它可以在扩展中写存储属性.
这个机制允许直接将两个 class 结合, 使用 with
关键字:
1 |
|
如果作为扩展的类不需要实例化, 则可以使用 mixin
关键字:
1 |
|
如果更符合扩展的特点, 则可以使用 on
关键字指定被扩展的类型:
1 |
|
类属性和类方法
使用 static
关键字, 和 swift 类似.
泛型
泛型集合类型
- 列表:
<String>[]
- 字典:
<String, String>{}
实际上等价于:
List<String>()
Map<String, String>()
实际上还有 Set<E>
类型.
泛型约束
1 |
|
泛型方法
1 |
|
库
关于访问控制的问题, 加了下划线的定义是库内可见, 而不加的话是公共的.
异步操作
有两种选择:
async
和await
: 实际是Future
的语法糖效果Future
库和Stream
库
使用 async/await
的时候, 返回 Future
对象:
1 |
|
如果有异常发生, 则仍然是和同步代码类似的处理方式:
1 |
|
若异步方法的返回值类型不是 Future
, 返回值也会自动被包裹到 Future
中, 类似 C# 中的任务并行库中的 Task
的效果.
并且还支持一些比较新的效果, 比如异步的 main 函数:
1 |
|
如果异步方法的确没有返回值, 最好是将其返回值写成 Future<void>
.
产生序列的优化: 懒生成(类似 C# 的 yield
)
可以使用如下两种方式懒生成: 实际上也是有一个 yield
关键字
- 生成
Iterable
对象: 同步生成 - 生成
Stream
对象: 异步生成
同步生成:
1 |
|
异步生成:
1 |
|
如果异步生成的时候还用到了递归, 则可以像下面这样写:
1 |
|
Dart 中的多线程机制: Isolate
详见这个链接.
typedef
1 |
|
还可以写泛型:
1 |
|
Flutter
根据教程来搞这个: https://flutter.io/docs/get-started/codelab
使用 VS Code 初始化工程.
打开模拟器:
1
open -a Simulator
真机的配置和模拟器类似, 只是多安装一些支撑工具即可.在官网上面有.
遇到一个坑, 如果真机和 Xcode 之前设置过无线连接的话, 则无法在 run 后保持和真机的持续连接.
配置国内的镜像, 否则无法获取三方库:
使用三方库:
在这个网站搜索三方库.
添加三方库和 Carthage 类似, 就加一句话到
pubspec.yaml
文件中的dependencies
或dev_dependencies
下面即可.比如:
1
english_words: ^3.1.5
但暂时不知道这个库是如何被加入到工程并使用的.
使用:
1
import 'package:english_words/english_words.dart';
字符串内插使用的是
$
符号, 貌似和C#
中的类似.无状态的控件是不可变的, 即它们的属性无法被改变, 所有的值都是常量, 不能改变的意思是: 在运行时无法通过 xx.yy = zz 来改变它属性的值.
有状态控件会去维护它的状态, 它的状态可能在自己的生命期内改变. 要实现有状态控件, 需要一个
StatefulWidget
类型来创建控件对象, 另外还需要一个State
类, 即状态类. 实际上有状态控件本身仍然是不可变的, 只是有了一个状态来维持它当前的状态.列表控件的性能: 是因为使用的 JIT, 尝试在 Release 下面编译安装测试
先在终端执行:
flutter build ios
默认情况下就是 release 模式下编译的.然后在 Xcode 中选择
Generic iOS Device
然后选择打包, 后续的步骤和普通 APP 一致.打包出来的 ipa 可以直接在 Xcode 的 Organizer 中拖进去就安装到手机了… 这个是个神技..
果然列表性能非常好.!!!!
在 Flutter 的世界中, Widget 类似 UIView, 但它们不会将自己直接渲染到屏幕上, Widget 只是用于描述视图. 且 Widget 不可变, 如果要拥有可变的 Widget, 唯一的途径就是给它们携带一个 State 对象. 在 State 中携带有当前 Widget 进行显示的内容, 故在 hot reload 的时候, 拥有 state 的 Widget 不会由于刷新而丢失数据.
在视图更新时, 框架会把需要更新的 Widget 替换掉. 而不会像 iOS 那样直接改变 View.
Flutter 可以和原生进行交互, 即相互调用. 通过的是消息通道, 类似 Android 中的 EventBus.
新建工程可以这样:
flutter create -i swift 工程名称
默认不设置的时候, 使用的是 OC.
具体可以看原生和 Flutter 交互.
越来越觉得 State 对象就是 Widget 的一个 viewmodel, 且已经做好了绑定了. 通过在
setState
方法内提供块参数修改 State 的内容, 就可以改变绑定的 Widget 的显示.Flutter 的 hot reload 是通过 JIT 编译提供的!
和原生交互的时候都是异步进行的.
一些特征
默认情况下, 苹果风格的导航栏默认就是标题居中, 而安卓风格的需要设置
AppBar
的centerTitle
属性.一个典型的 APP Bar 实现如下所示(里面使用苹果风格的按钮避免出现inkwell效果):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24AppBar _buildAppBar() {
return AppBar(
elevation: 1.0,
centerTitle: true,
title: Text('SHRINE'),
leading: CupertinoButton(
pressedOpacity: 0.8,
child: Icon(Icons.menu),
onPressed: _menuPressed,
),
actions: <Widget>[
CupertinoButton(
pressedOpacity: 0.8,
child: Icon(Icons.search),
onPressed: _searchPressed,
),
CupertinoButton(
pressedOpacity: 0.8,
child: Icon(Icons.explore),
onPressed: _explorePressed,
),
],
);
}网上介绍有一种思路可以是用 Flutter 写插件, 这样可以最大程度利用原生业务代理.