SDK:指一些被软件工程师用于为特定的软件包、软件框架、硬性平台及作业系统等建立应用软件的开发工具之集合。

dart

由于前边的教程使用了老版本Dart,所以部分代码可能存在空安全问题,可以参考”新特性”:

入口方法

Hello world程序的两种写法:

1
2
3
4
5
6
7
8
9
// 1
main() {
print('hello dart'); // 分号不可省略
}

// 2
void main() {
print('hello dart');
}

变量

  • 可以不预先定义变量类型,用var定义变量,类型会自动推导
  • 也可以定义变量类型
  • 不给变量赋值时,值为null(空)
1
2
3
4
5
6
7
8
9
10
void main() {
var str1 = 'hello dart1';
var num1 = 123;

String str2 = 'hello dart2';
int num2 = 456;

print(num1);
printf("$num2");
}
  • dart存在类型校验,类型确定后,再给变量赋值为另一个类型,会报错

final和const的区别

  • final可以开始不赋值,之后只能赋值一次
  • final不仅有const的编译时常量的特性,最重要的是它是运行时常量,并且final是惰性初始化,即在运行时第一次使用前才初始化
1
2
final a = new DateTime.now(); // 不报错
const a = new DateTime.now(); // 报错

运算符

a~/b:取整。如:5~/4 = 1

??=:如果左边值不为空,把右值赋给左边

??:和上方类似

1
2
var a;
var b = a ?? 10; // 如果a有值,就把a赋值给b,否则b为10

类型转换

xxx is int/double/String... :判断是否为某类型

int/double.parse():转化为整数/浮点数

xxx.toString():转化为字符串

xxx.isEmpty:判断当前字符串是否为空(无括号)

数组与集合

声明数组与扩容

数组可以直接声明,可以加类型:var a = <int>[1, 2, 3]; print(a);

也可以使用List:List b = <String>['4', '5', '6']; print(b);

1
2
var a = [{"b": 5}];
print(a[0]["b"]); // 不能用点,且属性名必须加引号

使用add添加元素:

1
2
List l = <String>['香蕉', '苹果'];
l.add('草莓');

如果要增加多个元素,使用addAll([元素1, 元素2]),即拼接数组

声明不可扩容的数组,使用filled:

1
2
List l = List.filled(2, '');
// l.add('草莓'); 报错

数组方法

isEmpty:判断数组为空

isNotEmpty:判断数组不为空

reversed:数组翻转

1
2
3
List l = ['西瓜', '苹果', '桃子'];
print(l.reversed); // (桃子, 苹果, 西瓜)
print(l.reversed.toList()); // [桃子, 苹果, 西瓜]

indexOf(value):查找元素,找到则返回序号,找不到返回-1

remove(value):根据值删除

removeAt(index):根据索引删除

fillRange(index1,index2,value):将一段区间内的元素的值修改,不包括右边界

1
2
3
List l = ['西瓜', '苹果', '桃子', '李子'];
l.fillRange(1, 3, '水果');
print(l); // [西瓜, 水果, 水果, 李子]

insert(index, value):指定位置插入

insertAll(index, list):指定位置插入数组

join(分隔符):数组转换成字符串

split(分隔符):字符串转换成数组

Set和Map

集合的创建和添加元素:

1
2
3
4
5
6
var s = new Set();
s.add('苹果');
s.add('香蕉');
s.add('香蕉');

print(s); // {'苹果', '香蕉'}

数组去重:

1
2
3
4
5
List myList = ['香蕉', '苹果', '西瓜', '香蕉', '苹果', '香蕉', '苹果'];
var s = new Set();
s.addAll(myList);
print(s); // {'苹果', '香蕉', '西瓜'}
print(s.toList()); // ['苹果', '香蕉', '西瓜']

映射(Maps)是无序的键值对

创建方式:

1
2
3
4
5
6
7
Map person = {
"name": "张三",
"age": 20
};

var m = new Map();
m["name"] = "李四";

常用属性:

  • keys:获取所有的key值
  • values:获取所有的value值
  • isEmpty/isNotEmpty

常用方法:

  • remove(key):删除指定key的数据

  • addAll({...}):合并map,给map增加属性

  • containsValue:查看map内的值,返回true/false

List、set、Map的通用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// List-map
List l = [1, 3, 5];
var newl = l.map((value) {
return value*2;
});

// List-forEach
l.forEach((value) {
print(value);
})

// Map-forEach
Map person = {
"name": "张三",
"age": 20
};
person.forEach((key, value) {
print("$key---$value");
})

// List-where
// 相当于js filter
var newl = l.where((value) {
return value > 3;
})

// List-every
// every:只要有满足条件的元素就返回true
var boolean = l.every((value) {
return value > 3;
});

// List-every
// every:只有元素全部满足条件才返回true
var boolean = l.any((value) {
return value > 3;
});

函数

参数

和js的区别:

  • 返回值可以限定类型
  • 传入的参数可以限定类型
  • 函数可以定义在入口函数外部(全局函数),也可以在内部,甚至定义在某个函数的内部,只是作用域不同

可选参数 + 默认参数:

1
2
3
4
5
6
7
8
9
10
11
// sex带默认参数
// age为可选参数
// 建议把带默认参数的变量放前面
String fn(String username, [String sex = '男', int age]) {
if (age != null) {
return "姓名:$username ---性别:$sex --- 年龄:$age";
}
return "姓名:$username --- 性别:$sex --- 年龄:保密";
}
print(fn('张三', 20));
print(fn('小李', '女'));

命名参数:

1
2
3
4
5
6
7
8
9
// 命名参数使用花括号
String fn(String username, {String sex = '男', int age}) {
if (age != null) {
return "姓名:$username ---性别:$sex --- 年龄:$age";
}
return "姓名:$username --- 性别:$sex --- 年龄:保密";
}
// 注意命名参数的传参方式
print(fn('张三', sex:'男', age:20,));

新版本dart应该这样写:

1
2
3
4
5
6
String fn(String username, {String sex = '男', required int age}) {
return "姓名:$username --- 性别:$sex --- 年龄: ${age}";
}
void main() {
print(fn('张三', sex:'男', age: 20));
}

箭头函数

dart的箭头函数内只能写一行,且不能带分号,所以可以在箭头函数中使用三目运算符

1
2
3
4
5
6
7
// 写法一
list.forEach((value) => print(value));

// 写法二
list.forEach((value) => {
print(value) // 不带分号
});

匿名方法

1
2
3
4
5
// 把一个匿名函数赋值给fn
var fn = () {
print(123);
}
fn();

IIFE

1
2
3
(() {
print('我是自执行方法');
})();

递归

1
2
3
4
5
6
7
8
9
10
11
var sum = 1;
fn(n) {
sum *= n;
if (n == 1) {
return;
}
fn(n - 1);
}

fn(5);
print(sum);

闭包

目的:使局部变量常驻内存,不污染全局变量

1
2
3
4
5
6
7
8
9
10
fn() {
var a = 1;
return() {
a++;
print(a);
};
}
var b = fn();
b(); // 2
b(); // 3

面向对象

类与构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person{
String name;
int age;
// 默认构造函数
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 简写形式:
// Person(this.name, this.age);

// 命名构造函数(可以定义多个)
Person.now() {
print('我是命名构造函数');
}

void fn() {
print("${this.name} --- ${this.age}");
}
}

void main() {
var d = new DateTime.now(); // 调用命名构造函数
Person p1 = new Person('张三', 20);
p1.fn();
Person.now(); // 调用命名构造函数
}

权限

Dart中没有public、private、protected这些访问修饰符号,定义私有成员,使用_(只有将类抽成单独的文件再引入时才有效)

将类写入.dart文件,放在/lib目录下,再引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
String _name; // 私有属性
int age;
Animal(this._name, this.age);

void fn() {
print("${this.name} --- ${this.age}");
}
String getName() {
return this._name; // 获得私有属性
}
void _run() {
print('这是一个私有方法');
}
execRun() {
this._run(); // 类里面方法的相互调用
}
}

主文件:

1
2
3
4
5
6
7
import 'lib/Animal.dart'

void main() {
Animal a = new Animal('小狗', 3);
print(a.getName());
a.execRun();
}

getter和setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Rect {
int height;
int width;
// 初始化列表,在构造函数体运行之前初始化实例变量
Rect(int height, int width):height = 2, width = 10 {
print("${this.height} --- ${this.width}");
this.height = height;
this.width = width;
}
// dart的getter类似于计算属性
get area { // 注意:没有括号
return this.height * this.width;
}
set areaHeight(value) {
this.height = value;
}
}

void main() {
Rect r = new Rect(10, 4);
r.areaHeight = 6;
print(r.area); // 24
}

静态成员

  • 静态成员可以直接用类名访问

  • 静态方法不能访问非静态成员,非静态方法可以访问静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
static String name = '张三';
int age = 20;
Person(this.name, this.age);
static void show() {
print(name);
}
void printInfo() {
print(name);
print(this.age);
}
}

void main() {
print(Person.name);
Person.show();
}

静态操作符

  • ?:条件运算符,当左侧不为null时就执行右侧。比如p?.fn(),只有当p不为null时才会执行其fn()方法
  • as:类型转换
  • is:类型判断
  • ..:级联操作(连缀)

连缀:

1
2
3
4
5
6
7
8
9
Person p1 = new Person('张三', 20);
p1..name = '李四'
..age = 30
..printInfo();

// 等价于
// p1.name = '李四';
// p1.age = 30;
// p1.printInfo();

继承

  • 子类使用extends关键字来继承父类
  • 子类会继承父类里可见的属性和方法,不会继承构造函数
  • 子类能复写父类的getter和setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print("${this.name} --- ${this.age}");
}
}

class Web extends Person {
String? sex;
Web(String name, num age, String sex) : super(name, age) {
// 实例化子类时给父类传参
// 除了给默认构造函数传参,还可以给命名构造函数传参
this.sex = sex;
}

@override // 覆写时建议加上这句(可不加)
void printInfo() {
// 重写父类的方法
print("${this.name} --- ${this.age} --- ${this.sex}");
}
}

void main() {
Web w = new Web('张三', 20, '女');
w.printInfo();
}

抽象类、多态与接口

没有方法体的方法,称作抽象方法

如果子类继承抽象类,必须实现里面的抽象方法

如果把抽象类当作接口实现,必须得实现抽象类里面定义的所有属性和方法

Dart中的多态:父类定义抽象方法,每个子类对这些方法的实现不同,子类实例赋值给父类的引用,这样子类实例只能调用这些方法,不能调用自己的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
abstract class Animal{
eat(); // 抽象方法
}

class Dog extends Animal {
@override
eat() {
print('小狗在吃骨头');
}
run() {
print('小狗在跑');
}
}
class Cat extends Animal {
@override
eat() {
print('小猫在吃鱼');
}
speak() {
print('小猫在叫');
}
}

void main() {
// 正常的写法是Dog a = new Dog(); Cat b = new Cat();
Animal a = new Dog(); // 只能调用eat,不能调用run
Animal b = new Cat(); // 只能调用eat,不能调用speak
}

Dart用抽象类实现接口:

接口就是标准,抽象类里面定义了一些属性和方法,要求子类必须对它们进行实现

extends 抽象类和implements的区别:

  1. 如果要复用抽象类里面的方法,并且要用抽象方法约束子类,用extends继承抽象类
  2. 如果只是把抽象类当作标准,就用implments实现抽象类

在Flutter中:

  1. class 就是 interface
  2. 当class被当做interface用时,class中的方法就是接口的方法,需要在子类里重新实现,在子类实现的时候要加@override
  3. 当class被当做interface用时,class中的成员变量也需要在子类里重新实现。在成员变量前加@override
  4. 实现接口可以有多个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
abstract class Db {
String? address;
write();
save();
}

class Mysql implements Db {
@override
write() {
print('Mysql write');
}
@override
save() {
print('Mysql save');
}
@override
String? address;

Mysql(String address){
this.address = address;
}
}

class MongoDb implements Db {
@override
write() {
print('MongoDb write');
}
@override
save() {
print('MongoDb save');
}

@override
String? address;

MongoDb(String address){
this.address = address;
}
}
void main() {
Mysql sq = new Mysql('localhost:127.0.0.1:3000');
sq.write();
MongoDb mg = new MongoDb('localhost:127.0.0.1:3001');
mg.save();
}

mixins

mixins意为混入,即在类中混入其他功能,使用with关键字来实现mixins

在Dart中可以使用mixins实现类似多继承的功能

使用mixins的条件:

  1. 作为mixins的类只能继承自Object,不能继承其他类
  2. 作为mixins的类不能有构造函数
  3. 一个类可以mixins多个mixins类
  4. mixins不是继承,也不是接口,而是一种全新的特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Person {
String name;
num age;
Person(this.name, this.age);
printInfo() {
print('${this.name} --- ${this.age}');
}
}

class A {
String info = 'this is A';
void printA() {
print('A');
}
}

class B {
void printB() {
print('B');
}
}

// 同时使用继承和mixins
class C extends Person with A, B {
C(String name, num age) : super(name, age);
}

void main() {
var c = new C('张三', 20);
c.printInfo();
// c可以同时调用A和B的方法,获取属性
print(c.info);
c.printB;

// mixins的类型,是其超类的子类型
print(c is Object); // true
print(c is A); // true
print(c is B); // true
print(c is C); // true
}

泛型

解决代码复用问题,并对特定数据类型进行校验

泛型方法

1
2
3
4
5
6
7
8
// 传入什么类型的数据,就返回什么类型的数据
T getData<T>(T value) {
return value;
}

void main() {
getData<String>('你好');
}

泛型类

1
2
3
4
5
6
7
8
9
10
// 限定数组内的元素的类型为String
List list = new List<String>.filled(2, '');
list[0] = "张三";
list[1] = "李四";
print(list);

List list2 = new List<int>.filled(2, 0);
list2[0] = 1;
list2[1] = 2;
print(list2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyList<T> {
List list = <T>[];
void add(T value) {
this.list.add(value);
}
List getList() {
return list;
}
}

void main() {
MyList l = new MyList<String>();
l.add('张三');
// l.add(1); 报错
print(l.getList());
}

泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
abstract class Cache<T> {
getByKey(String key);
void setByKey(String key, T value);
}

class FileCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print("我是文件缓存,把key=${key} value=${value}的数据写入了文件");
}
}

class MemoryCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print("我是内存缓存,把key=${key} value=${value}的数据写入了内存");
}
}

void main() {
FileCache f = new FileCache<Map>();
m.setByKey('index', {"name": "张三", "age": 20});

MemoryCache m = new MemoryCache<String>();
m.setByKey('index', '首页数据');
}

Dart的库分为三种:

  1. 自定义的库:import 'lib/xxx.dart';

  2. 系统内置库:

    import 'dart:math';

    import 'dart:io';

    import 'dart:convert';

  3. Pub包管理系统中的库

系统内置库

示例1:数学库

1
2
3
4
5
6
import 'dart:math';

main() {
print(min(1, 2)); // 1
print(max(1, 2)); // 2
}

示例2:ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'dart:io';       // http
import 'dart:convert'; // 将编码转化为utf8

void main() async {
var res = await getDataFromZhihuAPI();
print(res);
}

// API接口:https://news-at.zhihu.com/api/3/stories/latest
getDataFromZhihuAPI() async {
// 1.创建HttpClient对象
var httpClient = new HttpClient();
// 2.创建Uri对象
var uri = new Uri.http('news-at.zhihu.com', '/api/3/stories/latest');
// 3.发起请求,等待请求
var req = await httpClient.getUrl(uri);
// 4.关闭请求,等待响应
var res = await req.close();
// 5.解码响应的内容
return await res.transform(utf8.decoder).join();
}

Pub库

pub包管理系统中的库:

  1. 从下面网址找到要用的库:

    https://pub.dev/packages

    https://pub.flutter-io.cn/packages

    https://pub.dartlang.org/flutter/

  2. 创建一个pubspec.yaml文件,内容如下:

    1
    2
    3
    4
    5
    6
    name: xxx
    description: A new flutter module project.
    dependencies:
    # 填写被依赖的库
    # http: ^0.12.0+2
    # date_format: ^1.0.6
  3. 配置dependencies

  4. 在当前目录运行put get,获取远程库

  5. 参考文档使用库

库的特性

库的重命名

as可以实现库的重命名,避免名称冲突

部分引入:使用 show/hide 关键字

库里面定义了多个函数,我只引入一个:import 'lib/myMath.dart' show getAge

如果只是某个函数不想引入,则使用hide

延迟加载

即懒加载,作用是可以减少APP的启动时间

它使用deferred as关键字指定

1
2
3
4
5
6
7
8
import 'package:deferred/hello.dart deferred as hello';

// 当需要使用的时候,需要用loadLibrary()方法来加载

greet() async {
await hello.loadLibrary();
hello.printGreeting();
}

包分片

有时一个库太大了,使用part关键字把大库分成小的库

在一个大库中:

1
2
3
4
5
6
7
// 这里是index.dart

part 'part1.dart';
part 'part2.dart';
part 'part3.dart';

//...

dart新特性

可空类型?

文档:https://dart.cn/null-safety

简而言之,为了避免访问到空对象的值或方法而引起报错,dart在编译时就进行检查,避免出现空值

若你想让变量可以为 null,只需要在类型声明后加上 ?类型?表示可空类型

1
>int? aNullableInt = null;

类型断言!

一般配合try-catch使用

1
2
3
4
5
6
7
String? str = 'This is str';
str = null;
try {
print(str!.length); // 如果str != null 会打印长度,否则抛出异常
} catch (e) {
print('字符串为空');
}

late

late关键字的作用是延迟初始化,告诉编译器:这个值不为空,我一会儿再初始化

required

指定必须传入的命名参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
String name;
int age;
Person({required this.name, required this.age});

String getName() {
return '${this.name} --- ${this.age}';
}
}

void main(args) {
Person p = new Person(
name: '张三',
age: 20
);
print(p.getName());
}

flutter

以后学~