Dart 与 Flutter 之 dart 语言   2021-09-28


这是开发 todoline++ 项目所必须经历的一部分.

众所周知, 现在的软件开发必须提供移动应用和pc应用两个部分, 然而众多的平台使得单独开发变得非常的困难, 而且成本也出奇的高, 这使得市场急需一种一次开发, 适用所有平台的需求变得非常的迫切, 而到这个时候, 也就是 2021年的今天, 出现了很多这样的开发语言或者平台, 其中 facebook 的 react native 和 google 的 flutter 是市场中的领跑者. 我在调查了多个开发语言或者说是平台后, 决定采用 flutter 来开发 todoline++ 这个项目, 原因是 flutter 运行的性能要优于 react native, 因为 react native 编译后还是 js, js 需要在浏览器壳中运行, 运行的时候才将 js 解析成中间的字节码, 字节码再解析成机器码, 而 flutter 直接将代码编译为中间的字节码, 运行的时候就直接将字节码解析为机器码, 这样就少了一层解析, 运算速度就快多了!

可以肯定的是, 如果 react native 无法解决性能这个问题, 它注定是要被 flutter 超越, 这是我个人的看法. 但另外一方面, react native 使用 js 来开发, 非常受前端开发者的欢迎, 因此在使用数量上, react native 目前暂时超过了 flutter.

还有一点就是, 在操作系统领域, android 的用户数量是最多的, 而 android 是 google 开发, 即便 flutter 以后无法引领市场, 以 android 庞大的用户市场, 他也不是最差的一个。

由于 flutter 使用 dart 语言来开发, 而在此之前我还没有使用过 dart 语言, 因此, 这个博文记录了 dart 语言学习过程中的一些基础知识, 也算是对自己学习的一些总结。

本文知识点来自大地老师讲解的<Dart Flutter 视频教程>, b 站上有。

1. 如何安装 dart?

A) 官网:
~http://dart.dev/
~http://www.gekorm.com/dart-window

可以参考其他网站:
~https://codingdict.com/article/21952

B) vscode 需要安装两个插件: dart 和 code runner

问题:

A) dar 是谷歌开发, 连接不了怎么办?
请参考问题 B) 中的答案

B) android studio 如何安装 dart?
答案是不用单独安装 dart, 直接安装 flutter 即可, 下面是安装 flutter 的步骤。

  1. 新建一个文件夹, 用于 flutter 的安装目录
  2. 在刚新建的目录下使用 git 将 flutter 的文件下载下来: ~https://github.com/flutter/flutter.git
  3. 由于下载flutter 可能会受限, 配置两个临时用户环境变量, 这样下载速度会快一些
    1
    2
    PUB_HOSTED_URL=https://pub.flutter-io.cn
    FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
  4. 将 flutter 目录下的bin 目录配置到 path 环境变量中, 这个目录是 git 下载后的 bin 目录
  5. 运行下载目录中的 flutter_console.bat, 应该出现 flutter 图标, 表示安装成功
  6. 在命令行中输入 flutter doctor, 检查所需的依赖, 该命令会自动下载所需的依赖, 并提示缺失的依赖
  7. 根据步骤 6) 的提示, 安装缺失的依赖
    在这个步骤中我有两个提示:
    cmdline-tools component is missing
    Android license status unknown.
    随后, 我在 android studio 的 android sdk manager 中安装 android skd-command-line-tool(latest)
    再次运行, 发现报错: Unable to find bundled Java version. 然后重新安装 android studio, 从 3.6 更改为 2020版本, 最后问题解决。

2. dart 定义变量和常量

A) dart 的入口

1
2
3
void main() {
print("hello dart");
}

B) 定义变量

  1. 使用 var 来定义, dart 会自动进行类型推断: var str = “123456”;
  2. 强类型定义: String str = “123456”;
  3. 不管是弱类型定义还是强类型定义, dart 都会进行类型校验, 不匹配会抛出错误。

C) 命名规则
命名规则跟 java 一样, 下面是他的要求:

  1. 只能是数字,下划线和字母组成
  2. 不能是数字开头
  3. 不能是 dart 保留关键字
  4. 区分大小写
  5. 必须见名知意

D) 定义常量

  1. 使用 const 来定义: const PI = 3.14159;
  2. 使用 final 来定义: final PI = 3.14159;

这两个的区别是: const 是编译期间定义的常量, final 既可以是编译期间定义的常量, 也可以是运行期间定义的常量, 例如:

1
2
const date = new date(); // 一定报错
final date = new date(); // 不会报错

3. dart 数据类型

A) 字符串定义

1
2
3
4
5
6
7
8
9
var str0 = ""; // 可以使用双引号, 也可以使用单引号
String str1 = ""; // 强类型定义
String str2 = """dfasdf
fdfdf
fdfsd"""; // 三个双引号或者三个单引号可以定义跨行字符串

String str3 = "$str1 $str2"; // 字符串拼接
String str4 = str1 + "" + str2;

B) 数值类型

  1. int a = 123; // 整型数
  2. double b = 12.3 // 浮点数

数字类型可以使用通用的四则运算符号

C) 条件判断

1
2
3
4
5
6
var b = true;
if (b == true) {
// xxxx;
} else {
// yyy;
}

D) List 数据类型的定义

1
2
3
4
5
6
7
8
9
var list = ["str", 123]; // 直接赋值
var list = <String>["add", "bbb"]; // 指定元素类型, 只能使用字符串
var list = new List(); // 早期 dart 版本可以使用, 现在抛弃了
var list = List.filled[2, ""]; // 定义固定长度的 list;

var a = list[0]; // 获取某个元素的值
var b = list.length(); // 获取 list 的长度
list.add("ttt"); // 给 list 添加新的元素, 笃定固定长度的 list 不能添加
list.length = 0; // 清空 list, 仅限于动态 list, 固定 list 不可用。

E) Map 数据类型的定义

1
var map = {"a": "b", "b": 123}; // 跟 js 一样, 直接赋值

F) 数据类型判断

1
2
3
4
5
6
String str = "123";
if (str is string) {
// xxx
} else {
// yyy
}

4. dart 运算符

A) 算术运算符

1
2
3
4
5
6
+ // 加 
- // 减
* // 乘
/ // 除
% // 取余
~/ // 取整

B) 关系运算符

1
= < > >= <= != 

C) 逻辑运算符

1
2
3
||  // or
&& // and
! // not

D) 赋值运算符

1
2
var a = 10;  // 把 10 赋值给 a
var b ??= a // 如果 b 为空, 就把 a 赋值给 b.

E) 复合运算符

1
2
3
4
5
6
a = 10;
a += 1; // a = a +1;
a *= 10; // a = a * 10;
a /= 10; // a = a /10;
a %= 10; // a = a % 10;
a ~/= 10; // a = a ~/ 10;

F) 三目运算符

1
2
a = (b>10 ? 0 : 1); // 同 java
a = (b ?? 1); // 如果b不为空, a=b, 否则 a=1;

G) switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 53;
switch(a) {
case (a > 90) {
// xxxx
}
break;

case (a > 70) {
// yyyy
}
break;

default: {
// zzz
}
}

H) 类型转换

1
2
String str = "123";
int num = int.parse(str);

I) 捕获异常

1
2
3
4
5
try{
int num = int.parse(str);
}catch Exception1 {
// xxxx
}

J) 判空

1
2
3
4
5
6
7
8
9
int a;
if (a == null) {
// xxx
}

String str;
if (str.isEmpty()) {
// yyy
}

5. 自增,自减, for, while

A) 自增, 自减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 11;
var b = ++a;
print("$a $b"); // a=12; b=12 先自增后赋值

a = 11;
b = a++;
print("$a $b"); // a=12; b=11 先赋值, 后自增

var c = 11;
var d = --b;
print("$c $d"); // c=10; d=10; 先自减,后赋值

c = 11;
d = b--;
print("$c $d"); // c=11; d=10

B) for 语句

1
2
3
for(var i=0;i<100;i++) {
print(i);
}

C) while 语句

1
2
3
4
5
6
7
8
9
10
11
12
var b = false;
while(b == true) {
print("xxx");
break;
}
// print null

do {
print("xxx");
continue;
} while (b == true);
// print xxx

6. List, Set, Map

A) List 的常用属性:
length, isEmpty, isNotEmpty

B) List 的方法:
add addAll remove fillRange, indexOf, join,

C) Set 与 List 的区别:
Set 可以去重, List 不可以
set.addAll(list) 可以将 list 转换成 Set

D) Map 的属性
keys, values, length

E) Map 的方法
add remove fillRange

F) List, set, map 的遍历方法
forEach where any every

7. 方法/函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
String getInfo() {
return "info";
}

String generateUser(String name, {int age, String sex="男"}) {
return {
name: name,
age: age,
sex: sex
};
}

print(getInfo());
print(generateUser("wan"));
}

1) 方法体的格式:
返回值类型 方法名(参数类型 参数名称…) {
// xxxx
}

  1. 方法嵌套, 也就是方法体内可以定义方法, 不过请注意作用域。

  2. 方法的形参可以定义为可选, 请使用大括号来包围起来。

  3. 方法的形参可以设置默认值, 采用等于号来赋值。

8. 箭头函数,匿名函数,自执行函数,闭包

A) 箭头函数
下面的两个函数的写法, 其功能是一样的

1
2
3
list.foreach((value){
print(value);
})
1
list.foreach((value)=> print(value));

B) 匿名函数

1
2
3
4
5
6
7
void main() {
var fn = (){
print("add");
}

fn();
}

C) 自执行函数

1
2
3
((txt){
print(txt);
})("dfdf");

D) 闭包函数
为什么需要闭包?
如果我们定义一个全局变量, 多个函数可以共享使用, 但他会产生并发问题, 而如果我们定义为局部变量的话, 又无法共享使用, 怎么办?

这就是闭包需要解决的问题。

1
2
3
4
5
6
7
8
9
10
11
void main() {
void p() {
int i = 0;
return increaseAndPrint() {
i++;
}
increaseAndPrint();
}

p();
}

9. 类

A) 类的定义

1
2
3
4
5
6
7
8
9
10
11
class ClazzA {
int attrA = 0;

String attrB = 0;

ClazzA(this.attrA, this.attrB);

void methodA() {
print("aaaa");
}
}

B) 构造函数
默认构造函数: 上面的例子中, ClazzA(this.attrA, this.attrB); 就是默认的构造函数, 初始化实例时他会自动将形参赋值给类变量。

命名构造函数: 类名.构造函数名称() {}

C) 计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ClassA {
int attrA = 0;

String attrB = 0;

get num {
return attrA + attrB;
}

set attr(int a, int b) {
this.attrA = a;
this.attrB = b;
}
}

get num 就是计算属性, set 方法可以对成员变量赋值

D) 引用类和私有变量和私有函数
使用 import 来引入外部的类
变量名和函数名前面加下划线”_”, 就可以变成私有变量和私有函数。

E) 初始化列表
什么是初始化列表?
形式如下:

1
2
3
4
class A {
final int a;
void A(int x, int y): a = x * y;
}

为什么需要初始化列表?
dart 类的 final 变量, 如果是通过两个变量计算得来, 而且他有无法在构造函数中赋值, 因此只能要通过初始化列表来实现。

10. 静态成员,静态方法, 继承

A) 如何定义静态成员和静态方法?
添加 static 就可以让成员变量和方法变成静态。

注意:
非静态方法可以调用静态成员和静态方法, 但是静态方法不可以调用非静态成员和非静态方法

B) dart 如何继承和重写?
使用 extands 关键字来继承, 重写使用注解 @override。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
String name;

Person(this.name);

void doWork() {
print("${this.name} starting to work...");
}
}

class Male extands Person {
String sex;

Male(String name, this.sex):super.name=name;

@override
void doWork() {
print("${this.name} is male, starting to work...");
}
}

C) 子类调用父类的方法
super.xxxx();

11. 抽象以及多态

A) 抽象类的定义

1
2
3
4
5
6
abstract class Anninal {
eat();
run() {
print("running...");
}
}

B) dart 没有 interface 的概念

C) 抽象类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Cat implements Anninal {
@override
eat() {
print("cat eatting...");
}
}

class Dog implements Anninal {
@override
eat() {
print("dog eatting...");
}
}

D) 多态
就是多个子类实现了同一个父类的抽象方法, 在使用的时候通过父类的方法去执行, 但得到的结果却是不一样, 这就是多态。

1
2
3
4
5
Anninal a1 = new Cat();
a1.eat(); // cat eatting...

Anninal a2 = new Dog();
a2.eat(); // dog eatting...

E) 抽象类需要注意的问题

  1. 抽象类无法实例化
  2. 抽象类可以拥有普通方法
  3. 抽象类需要使用 abstract 关键字来修饰

12. extends, mixins

A) 什么是 mixins ?
java 中是可以实现继承的, dart 也一样, 但是继承只能单继承, 不可以多继承, 怎么办呢? 那就只能使用 mixins 了, 但如果我们说 mixins 就是为了实现多继承的话, 有人说它的格局不仅仅是 mixins, 具体怎么理解 mixins , 目前还不清楚。

B) 如何使用 mixins?
就像使用 extends 关键字来实现继承一样, 可以使用 with 关键字来实现 mixins。
下面是个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A {
void methodA() {
print("a");
}

void doSomething() {
print("doSomething");
}
}
class B {
void methodB() {
print("b");
}
void doSomething() {
print("doSomething");
}
}

class C with A, B {
// xxx
}

C) 使用 mixins 需要注意的问题

  1. with 关键字后面的类, 在定义的时候, 不能有构造函数
  2. 如果with关键字后面的类, 每个类都拥有相同的方法, 那么执行 mixins 类的这个方法, 调用的就是with关键字后面排在最后面的类的方法, 上面的例子中, A, B 类都拥有 doSomething 方法, C 类是 mixins 类, 执行 C.doSomething() 就是调用 B.doSomething(), 因为 B 就是排在最后面。
  3. (C is A) = true, (C is B) = true, 这两者都成立。

13. 泛型

A) 何为泛型?
就是一个方法的参数, 或者一个类的成员变量, 不指明它的具体类型, 运行使用者在使用时在确定类型, 以便于方法或者类的使用更加通用。

B) 什么情况下使用泛型?

1
2
3
4
5
6
7
8
9
10
11
class A {
String toString(int param) {
return param + "";
}
}

class B {
String toString(double param) {
return param + "";
}
}

在上面的例子中, toString 方法中的参数类型是不同的, 但我又无法使用其中的一个方法来实现, 这个时候你可能会说只需保留B类就可以了, 因为他的参数类型是 double, 传入 int 类型也是可以的。
但是你知道吗? 传入 int 得出来的数据会有小数点, 我就是不想有小数点, 怎么办?

于是, 我使用泛型就可以解决这个问题了:

1
2
3
4
5
class B {
T toString(T param) {
return param + "";
}
}

上面的代码中 T 就代表了可以传入任意类型的参数, 返回类型也是 T, 这样编译器就可以知道传入的参数类型和返回值类型是一致的, 只要不一致, 就可以抛出异常了。

C) 那么, 成员变量如果定义为泛型, 怎么办?

1
2
3
4
5
6
class B<T> {
T parma;
void method(T p) {
ths.parma = p;
}
}

D) 抽象类以及泛型

1
2
3
4
5
6
7
8
9
10
11
12
abstract class B<T> {
T parma;
void method(T p);
}

class C<T> implements B<T> {

@override
void method(T p) {
super.param = p;
}
}

派生类实现基类的时候, 如果基类是泛型, 定义派生类也需要指明泛型, implements 后面的基类类名必须加上泛型的标识, 同时派生类的类名后面也得加上泛型的标识。

14. 第三方库

A) 如何引入值定义库?
import “lib/xxx.dart”; // lib为项目跟目录下的子目录

B) 如何引入系统库?
import “dart:io”; // io 库是系统库的一部分

注意:

  1. IO 库会涉及到异步和同步的问题, 异步方法请使用 async 关键字来修饰, 系统会单独开辟一个线程来运行
  2. 等待异步线程结果请使用 awaite 关键字。
  3. 使用 awaite 关键字所在的方法, 必须使用 async 关键字来修饰。

C) 第三方库所在的地址
第三方库使用 pub 包来管理, 下载地址如下:

1
2
3
https://pub.dev/packages
https://pub.flutter-io.cn/packages
https://pub.dartlang.org/flutter/

D) 引入第三方库的步骤

  1. 在项目根目录下新建一个 pubspec.yaml 文件
  2. pubspec.yaml 文件中定义依赖包的名称和描述信息
  3. 使用 pub get 命令下载依赖包
  4. 导入依赖包 import “package:xxx” as yyy
    请注意
    导入包时 as 是将依赖包进行重命名, 这个在类名冲突的情况下可以更方便的使用依赖包
    导入包时 hide 关键字可以将某个包的某些方法去掉, 也就是不导入该方法
    import “xxx/xxx.dart” hide foo; // 除了 foo, 其他都要导入
    导入包时 show 关键字只是导入某个方法
    import “xxx/xxx.dart” show foo; // 只是导入这个包下的 foo

15. dart 的新特性

A) 可空, 非空
我们定义了的变量, dart 默认是非空的, 因此当我们试图给他赋值为空的时候, 就会报警

1
2
String str = "str"
str = null; // 会警告不能为 null

那么, 如果我们允许变量为 null, 怎么办呢?
数据类型后面加上问好(?)即可

1
2
String? str = "str"
str = null;

B) 数据类型断言

C) 延迟初始化数据
dart 语言默认变量不可为空的时候, 就会导致一个问题, 如果代码运行了以后, 才会赋值怎么办? 在赋值之前, 变量一直是空的。
late 关键字就是延迟初始化变量。

1
late String str; // 这个是延迟初始化, 不会报错

D) 方法中的命名参数不可为空
方法中的命名参数, dart 默认不可为空, 但如果我们使用括号{}将参数括起来后, 就会变成可选参数, 有这么一种情况, 如果我们想让某个参数不为空, 但另外的参数可选, 也就是可以为空, 怎么办?
答案是使用 requre 关键字。

1
2
3
4
5
6
7
8
9
10
class A {
int a;
int? b;
String c = null;
void method(String c, {requre int a, int b}) {
this.c = c;
this.a = a;
this.b = b;
}
}

在上面的代码中 method 函数中的 a 要求必填, b 可以为空

但是这里产生一个问题, 既然我要求他必填, 为什么要把他放进括号中让他变成可选参数呢?


分享到:


  如果您觉得这篇文章对您的学习很有帮助, 请您也分享它, 让它能再次帮助到更多的需要学习的人. 您的支持将鼓励我继续创作 !
本文基于署名4.0国际许可协议发布,转载请保留本文署名和文章链接。 如您有任何授权方面的协商,请邮件联系我。

Contents

  1. 1. 如何安装 dart?
    1. 问题:
  • 2. dart 定义变量和常量
  • 3. dart 数据类型
  • 4. dart 运算符
  • 5. 自增,自减, for, while
  • 6. List, Set, Map
  • 7. 方法/函数
  • 8. 箭头函数,匿名函数,自执行函数,闭包
  • 9. 类
  • 10. 静态成员,静态方法, 继承
  • 11. 抽象以及多态
  • 12. extends, mixins
  • 13. 泛型
  • 14. 第三方库
  • 15. dart 的新特性