Skip to content

简介 ​

官方文档 dart.cn

环境搭建 ​

安装 flutter SDK ​

1.直接去官网下载并解压 Flutter SDK 即可。Flutter SDK 自带了 Dart SDK 所以无需单独安装 Dart SDK

2.打开电脑高级系统设置配置环境变量找到 path 变量编辑添加一条并粘贴 Flutter SDK 安装位置(如:futterSDK/futter/bin)即可

3.确认是否安装完成

bash
flutter doctor -v

4.配置镜像。由于在国内访问 flutter 可能会受到限制,所以我们需要配置镜像。

先在系统高级设置中设置环境变量,添加两个变量。具体变量名与对应值:

PUB_HOSTED_URL=https://pub.flutter-io.cn

FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

安装 vscode 插件 ​

打开 vscode 直接搜索 Flutter 安装插件即可。安装 Flutter 插件后会自动安装 Dart 插件。

安装 JDK ​

1.Android SDK 的命令行工具依赖 Java ,所以要先安装好 JDK。官网:https://www.oracle.com/java/technologies/downloads/#jdk25-windows。直接下载 .exe 文件然后安装即可。

2.配置环境变量。修改 Path 变量,添加 bin 目录的地址。如: D:\JDK\bin

3.打开命令行工具输入 java 验证。

bash
java

安装安卓 SDK ​

可以直接安装 Android Studio 借助它安装安卓 SDK。如果安装了 Android Studio 这一步就可全部跳过。

1.如果不想安装 Android Studio 可以去官网滑倒底部寻找 “命令行工具” 下载命令行工具包。官网:https://developer.android.google.cn/studio?hl=zh-cn

2.解压及配置:

1)将解压缩的 cmdline-tools 目录移至您选择的新目录,例如 android_sdk。这个新目录就是您的 Android SDK 目录。

2)在解压缩的 cmdline-tools 目录中,创建一个名为 latest 的子目录

3)将原始 cmdline-tools 目录内容(包括 lib 目录、bin 目录、NOTICE.txt 文件和 source.properties 文件)移动到新创建的 latest 目录中。现在,您就可以从这个位置使用命令行工具了

官网安装教程:https://developer.android.google.cn/tools/sdkmanager?hl=zh-cn

2.配置环境变量:

1)变量名:ANDROID_HOME 变量值:C:\Android(解压的根目录)

2)修改 Path 变量,添加:%ANDROID_HOME%\cmdline-tools\latest\bin 以及 %ANDROID_HOME%\platform-tools

3.打开命令行工具输入 sdkmanager --list 验证

bash
sdkmanager --list

4.安装必要组件。这些组件会安装到第二步配置的 ANDROID_HOME 环境变量对应的目录中。

bash
# platforms;android-34 :提供特定Android版本的框架API和系统库,是应用开发的基础
# build-tools;34.0.0 :包含编译工具链(如aapt、dx、zipalign),负责将Java/Kotlin代码编译为APK,并进行资源打包、签名优化。
# platform-tools :包含Android调试桥(ADB)、Fastboot等核心工具,是设备通信、应用安装/调试、日志查看的基础。
sdkmanager "platforms;android-34" "build-tools;34.0.0" "platform-tools"

安装模拟器 ​

如果上一步选择安装 Android Studio 这一步可跳过。

这里以 MuMu 模拟器为例。

1.先安装 MuMu 模拟器。

2.打开模拟器设置中找到网络选项下“网络桥接模式” 并将其打开,重启模拟器。

3.此时用 vscode (需安装 flutter 插件)打开一个 flutter 项目,在 vscode 右下角即可选择刚刚启动的模拟器。

创建第一个项目 ​

第一次创建 flutter 项目建议使用 Android Studio 创建,可以避免很多问题。第一次运行项目会很慢。

当然也可以通过命令行直接创建

bash
flutter create 工程名

运行项目

bash
flutter run

运行第一个程序 ​

新建 holle.dart 文件并打开终端运行

dart
// 程序的入口:main 函数
void main() {
  // 打印字符串
  print('holle');
}

其他注意事项 ​

适配更多端 ​

如果创建项目时没有适配其他端,可打开原有项目运行如下命令,让项目可以适配其他端

bash
flutter create .

设置安卓gradle国内镜像和maven仓库 ​

项目启动或者打包安卓应用时,会下载一些必要的东西,但速度极慢,需要替换为国内镜像

1.打开 flutter 项目 android/gradle/wrapper/gradle-wrapper.properties 设置 distributionUrl 为国内镜像地址

properties
# gradle-wrapper.properties 文件
distributionurl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14-all.zip

2.打开 flutter 项目 android/gradle/wrapper/settings.gradle.kts 修改 gradle 的 maven 仓库镜像地址

properties
# settings.gradle.kts 文件
repositories {
	# 在这之间添加如下代码
    maven { url = uri("https://maven.aliyun.com/repository/google") }
    maven { url = uri("https://maven.aliyun.com/repository/releases") }
    maven { url = uri("https://maven.aliyun.com/repository/central") }
    maven { url = uri("https://maven.aliyun.com/repository/public") }
    maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
    maven { url = uri("https://maven.aliyun.com/repository/apache-snapshots") }
    maven { url = uri("https://maven.aliyun.com/nexus/content/groups/public/") }
    maven { url = uri("https://jitpack.io") }
    # 在这之间添加如下代码
    google()
    mavenCentral()
    gradlePluginPortal()
}

配置安卓网络权限 ​

打开 flutter 项目 android/app/src/main/AndroidManifest.xml 修改 gradle 的 maven 仓库镜像地址

xml
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 添加此条代码 -->
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

运行到鸿蒙端 ​

1.需要下载鸿蒙版的 flutter SDK,并修改 flutter SDK 环境变量

bash
git clone https://gitcode.com/openharmony-tpc/flutter_flutter.git

2.安装 DevEcoStudio 下载地址:https://developer.huawei.com/consumer/cn/download/

并在编辑器中安装鸿蒙模拟器

3.配置鸿蒙环境变量。

4.如果一开始是通过原版 flutter 创建的项目,可能需要降低 Dart SDK 的版本以及一些插件的版本。直接打开项目下 pubspec.yaml 文件修改对应版本即可,然后运行如下命令。

bash
# 清理依赖并重新安装依赖
flutter clean && flutter pub get

# 可选 如果一开始是通过原版 flutter 创建的项目,此处需要执行如下命令来适配鸿蒙端
# flutter create .

5.解决兼容性问题。有些插件可能不支持鸿蒙端,但是如下仓库内有许多社区提供了适配版本,需要打开项目下 pubspec.yaml 文件将对应有适配问题的插件使用 git 仓库模式安装

yaml
shared_preferences:
	git:
		url:"https://gitcode.com/openharmony-sig/flutter_packages.git"
		path:"packages/shared_preferences/shared_preferences"

main 函数 ​

给 main 函数设置参数并传参

dart
// 先写入参数,后打开命令行运行此文件:dart test.dart 1,23
// 文件名后面就可以传递参数
void main(List<String> args) {
    print(args);// [1,23]
}

基础语法 ​

变量 ​

使用 var 定义一个变量。变量名必须由字母、数字、下划线、$ 组成。不可以以数字开头。

dart
// 变量默认值是 null
// 变量的值不会进行隐式转换
void main() {
  // 定义变量,变量支持类型推断
  var a = 1;
  a = 2;
  // a='ddd' 报错
  print(a);
}

常量 ​

定义常量使用 const 和 final

dart
void main() {
  const a = 1;
  final b = 2;
  const f = 3;
  // a=2; 报错,常量不可修改
  // b=3; 报错,常量不可修改
  var c = 1;
  var d = 2;
  // const res = a + b; 报错 const 定义的常量是编译期常量,值是在编译时赋值
  const res2 = a + f; // 如果都是编译期常量可以赋值给 const
  final res3 = a + b; // 不报错,final 定义的常量是运行时常量,值是在运行时赋值
  print(res2); // 4
  print(res3); // 3
  const time = DateTime.now();// 报错,无法将运行时的值分配给 const 常量
  final time2 = DateTime.now();// 成功,可以将运行时的值分配给 final 常量
}

枚举 ​

dart
// 通过 enum 声明枚举,它是数量固定的常量值
enum Color { red, green, blue }

void main() {
  // 访问枚举某个值的索引
  print(Color.green.index); // 1
  // 访问枚举整个列表
  print(Color.values); // [Color.red, Color.green, Color.blue]
}

数据类型 ​

dart 中分为如下 5 中常用数据类型

dart
// num (数值)	bool(布尔) String(字符串) List(列表) Map(字典)

数值 ​

dart
void main() {
  num n = 2; // num 用来定义整数或浮点数
  n = 2.3;
  int n2 = 3; // int 用来定义整数
  // n2 = 3.3; // 报错,int 不能用来定义浮点数
  double n3 = 4.4; // double 用来定义浮点数
  n3 = 4;// 如果 double 赋值一个整数,实际上还是转成了浮点数保存
  print(n3); // 4.0
  
  // 常用 api
  n3.toString(); // 类型转换
  n3.abs(); // 绝对值
  n3.toStringAsFixed(4); // 保留 4 位小数
    
  int f = 2.5.toInt();// 会将浮点数转换成整数,当是小数时但会舍弃小数部分
  print(f); // 2
    
  double f1 = 2.toDouble();// 会将数值转换成浮点数,当是整数时会在末尾添加 0
  print(f1); // 2.0

  num n = 0 / 0;
  print(n); // NaN
  print(n.isNaN); // true
}
// int.parse('2', radix: 10)    将字符串转换为整数。radix 表示转换为的进制,比如这里就是转换为 10 进制
// double.parse('2')    将字符串转换为浮点数。

布尔值 ​

dart
void main() {
  bool a = true;
  a = false;
}

字符串 ​

dart
void main() {
  String str = 'holle';
  print(str);
  // 字符串拼接
  double price = 199.0;
  String res = '${price}元';
  // 可写成 '$变量' 或 '${表达式}'
  print(res); // 199.0元
  String s = """
  三个引号可以换行
  """;
  // api
  print("str".split(''));// [s, t, r] 和 js 中 split 方法一致
  print('str'.replaceAll('s', '')); // tr     和 js中替换字符串api一致
  // 支持正则替换
  print('s2t3r44'.replaceAll(RegExp(r'\d+'), '')); // str
  print('erf'.contains('e'));// true
  print('erf'.indexOf('e')); // 0
  
  var str = '';
  // String.isEmpty是判断是否是空白字符串,他并不是null,不是空,依然有值,只不过值是空白字符串""
  print(str.isEmpty); // true
  // isNotEmpty跟isEmpty完全相反
  print(str.isNotEmpty); // false

  // String.split     	   字符串切割成列表
  // String.replaceAll     字符串替换
  // String.contains       字符串查找,检测字符串是否包含某个字符
  // String.indexOf        找出某个字符出现的索引
  // String.isEmpty        判断是否是空白字符串(即 "")
  // String.isNotEmpty     与 isEmpty 相反
}

列表 ​

dart
void main() {
  List arr = [1, '2', 3];
  print(arr.length); // 列表长度
  print(arr[2]); // 查询列表某一项
  arr[2] = '3'; // 修改
  print(arr);
  arr.add('3'); // 在列表末尾添加一个元素
  print(arr);
  arr.addAll(['t', 'y']); // 在列表末尾添加多个元素
  print(arr); // [1, 2, 4, value, t, y]
  arr.remove('3'); // 根据元素名删除(删除一个),这里是删除字符串类型的3
  print(arr);
  arr.removeAt(0); // 根据索引删除元素
  print(arr);
  List a = [1, 2, 3, 4, 5];
  a.removeRange(0, 2);
  print(a);// [3, 4, 5]
  // 列表遍历
  arr.forEach((element) {
    print(element);// 打印列表的每一项
  });
  // 注意如下代码中打印语句不会执行(forEach 不会出现此情况)可使用 b 变量或者在后方调用 toList才会执行
  List a = [1, 2, 3, 4, 5];
  var b = a.map((e) {
    print('1');
    return e * 2;
  });
    
  var list = List.generate(3, (index) => index);
  print(list); // [0, 1, 2]

  List a = [1, 2, 3];
  List a2 = [...a, 4, 5, 6];
  Map o = {'a': 2};
  Map o2 = {...o, 'k': 4}; // 展开运算符和js中使用类似
  print(a2); // [1, 2, 3, 4, 5, 6]
  print(o2); // {a: 2, k: 4}
  // 创建列表,当 empty 方法参数 growable 为true时表示创建一个可以改变长度的列表
  var l3 = new List.empty(growable: true); 
  l3.add(1);
  print(l3); // [1]
  var l4 = new List.filled(3, 6);
  print(l4); // [6, 6, 6]
  List a = [1, 2, 3];
  var l;
  print([...?l, ...a]); // [1, 2, 3]
  print([2, 3, 4].reversed); // (4, 3, 2)
  print([2, 3, 4].reversed.toList()); // [4, 3, 2]
  List a = [1, 2, 3];
  a.insert(1, 4);
  a.insertAll(1, [5, 6]);
  print(a);
  print(['123', '456'].join('-')); //123-456
  var a = [1, 2, 3].map((e) {
    return e * e;
  });
  print(a); //(1, 4, 9)
  print([1, 2, 3].where((element) {
    return element <= 2;
  })); //(1, 2)
  print([1, 2, 3].any((element) {
    return element <= 2;
  })); //true
  print([1, 2, 3].every((element) {
    return element <= 2;
  })); //false
  var a = [[1],[2]];
  var a2 = a.expand((element) => element);
  print(a2); // (1,2)
  print([1, 2, 3].fold<num>(2, (p, element) => p * element)); // 12
}
// List.add  		在列表末尾添加一个元素
// List.addAll 		在列表末尾添加多个元素
// List.remove 		根据元素名删除(删除一个)
// List.removeRange 删除一个范围的元素:removeRange(0, 2),将删除列表索引0到2(不包含2)的元素
// List.removeAt 	根据索引删除元素
// List.removeLast 	删除列表最后一个元素
// List.forEach 	列表遍历
// List.isEmpty 	判断列表是否为空,返回一个布尔值
// List.isNotEmpty 	判断列表是否不为空,返回一个布尔值
// new List.empty   创建列表的一种方式,当参数为true时表示创建的列表可以改变长度(可以添加元素)
// new List.filled  创建列表的一种方式,参数一表示创建列表的长度,参数二表示要填充的元素
// [...a]		    和js展开运算符类似,还可以展开字典
// [...?a]			当a不能被展开时不会执行展开操作避免报错
// List.reversed    列表翻转,反转后不是列表
// List.reversed.toList()    将reversed反转后的数据转化为列表,也可使用 [...a]来转换成数组
// List.insert      在列表指定索引插入元素
// List.insertAll   在列表指定索引插入多个元素
// List.join		将数组拼接成字符串,和 js 中使用类似
// List.map			遍历列表对列表进行一些操作并返回一个由括号包裹的可迭代数据,需要转换为列表
// List.where		遍历列表返回符合条件的可迭代数据,需要转化为列表
// List.any		    查找该列表是否有符合条件的元素,返回一个布尔值
// List.every		查找该列表里的元素是否全符合条件,返回一个布尔值
// List.expand		列表降维默认只能将二维转化成一维
// List.fold		和 js 中 reduce 相似
// List.generate	生成指定长度的列表,使用:List.generate(3, (index) => index)

Set ​

dart
// Set 是一个无序的,元素唯一的集合。
void main() {
  // 字面量方式创建 Set
  var s = <int>{1, 2, 3};
  // 构造函数创建。在 Dart 中 new 可以省略
  var fr = new Set();
  fr.add('1');
  fr.add('2');
  fr.addAll([3, 4]);
  print(fr); // {1, 2, 3, 4}
  print([1, 2, 2, 3].toSet().toList()); // [1, 2, 3] 利用Set给列表去重
  // 求交集
  print({1, 2, 3, 4}.intersection({1, 2, 5, 6})); // {1, 2}
  // 求并集
  print({1, 2, 3, 4}.union({1, 2, 5, 6})); // {1, 2, 3, 4, 5, 6}
  // 求差集
  print({1, 2, 3, 4}.difference({1, 2, 5, 6})); // {3, 4}
  // 返回第一个
  print({1, 2, 3, 4}.first); // 1
  // 返回最后一个
  print({1, 2, 3, 4}.last); // 4
  // 集合不能通过下标取值
  print({1, 2, 3, 4}[1]); // 报错
  // 和 js 类似可以用 Set 对列表去重
  var arr = [0, 0, 1, 2, 2];
  var fr1 = {...arr};
  print(fr1); // {0, 1, 2}
  // 可以用 for in 遍历 Set
  var s = {1, 2, 3, 4, 5};
  for (var item in s) {
    print(item);
  }
}

Map ​

dart
// Map 是键值对,泛型第一个类型是键,key,第二个是值,value。其中键、值都可以是任何类型
var m = Map<int, String>();
// 添加或修改键值对
m[0] = 'one';
print(m); // {0: one}

Map m = {};
// 判断当前 Map 是否为空
print(m.isEmpty); // true

// 还可以这样定义 Map
var map = {'name': '张三', 2: 18};
// addEntries 添加一组数据
map.addEntries([MapEntry('age', 18), MapEntry('sex', '男')]);
print(map); // {name: 张三, 2: 18, age: 18, sex: 男}

var map = {'name': '张三', 2: 18};
// 遍历 Map 并返回一个新的 Map
print(map.map((key, value) => MapEntry(key, '123'))); // {name: 123, 2: 123}

var map = {'name': '张三', 2: 18};
// entries.map 返回一个选代器,可以是任何类型,这样就可以把 Map 转换为其他类型的选代器,再转为数组
var it = map.entries.map((e) {
    return 'key:${e.key},value:${e.value}';
});
print(it); // (key:name,value:张三, key:2,value:18)

var l = [1, 2, 3];
// asMap 可以将列表转换为 Map
print(l.asMap()); // {0: 1, 1: 2, 2: 3}

var map = {'name': '张三', 2: 18};
// forEach 遍历 Map
map.forEach((key, value) {
    print('key:$key,value:$value');
});

Map m = {"1": 'r', "2": 'e', "3": 'p'};
// for in 循环遍历 Map
for (var key in m.keys) {
    print('key:$key, value:${m[key]}');
}

// Map.containsValue		检测字典中是否含有某个值
// Map.keys					列举字典中所有的键
// Map.values				列举字典中所有的值
// Map.removeWhere			遍历字典根据条件删除对应元素

dynamic ​

dart
// dynamic 可以保存任意类型的数据,编码灵活
void main() {
    // 使用 dynamic 声明的变量会关闭编译器的类型检查
    dynamic a = 20;
    // runtimeType 可以获取变量运行时的类型
    print(a.runtimeType); // int
    a = '30';
    print(a); // 也是可以正常使用的
    print(a.runtimeType); // String
}

其他数据类型 ​

基本使用

可选类型 ​

dart
// 可选类型,可空类型
int? i;
print(i); // null
// 对比普通 int 类型可以赋值为 null
i = null;
// 可选类型是不可以直接比较的,因为有可能是 null
print(i>10);// 报错,编译不通过
// 如果你确定一定不为 null 可以使用 ! 强制解包来解决
print(i!>10);// 编译通过,但运行出错

类型别名 ​

dart
void main() {
    int add(int a, int b) {
        print(a + b);
        return a + b;
    }
    // 使用
    Add fn = add;
    fn(1, 2);
}

// 类型别名,注意不能写在 main 函数内
typedef Add = int Function(int, int);

as 类型转换 ​

dart
dynamic d1 = '1223';
var s = d1 as String;

安全机制 ​

dart
void main() {
  String str2 = 'lol';
  print(str2.length); // 3
  String str;
  print(str.length); // 报错
  // 解决
  // 使用 ? 明确指定的变量可以为空
  String? str3;
  // 表示非空检查,如果 str3 为空则不会调用属性或方法
  print(str3?.length);
}

运算符 ​

算术运算符 ​

dart
void main() {
  int n1 = 10;
  int n2 = 3;
  print(n1 + n2); // 13 加
  print(n1 - n2); // 7 减
  print(n1 * n2); // 30 乘
  print(n1 / n2); // 3.3333333333333335 除
  print(n1 ~/ n2); // 3 取整:取除法结果的整数部分
  print(n1 % n2); // 1 取余
  // print(n1++); // 10 自增
  // print(n2--); // 3 自减
  print(++n1); // 11 自增
  print(--n2); // 2 自减
}

赋值运算符 ​

dart
void main() {
  int n1 = 10;
  double n2 = 10;
  // print(n1 += 1); // 11 加等于
  // print(n1 -= 1); // 9 减等于
  // print(n1 *= 2); // 20 乘等于
  print(n2 /= 2); // 5.0 只有 double 类型数据才能进行除等于运算
}

比较运算符 ​

dart
void main() {
  int n1 = 10;
  int n2 = 20;
  print(n1 > n2); // false 大于
  print(n1 < n2); // true 小于
  print(n1 >= n2); // false 大于等于
  print(n1 == n2); // false 等于
  print(n1 != n2); // true 不等于
}

逻辑运算符 ​

dart
void main() {
  int n1 = 0;
  int n2 = 5;
  print(n1 > 28 && n2 > 4); // false 一假即假
  print(n1 > 28 || n2 > 4); // true 一真即真
  print(!(n1 > 28 || n2 > 4)); // false 取反
}

地板除 ​

dart
// 地板除是先进行除法再将结果向下取整
print(7 ~/ 4); // 1

is 运算符 ​

dart
var str = 'Hello World';
// is 运算符可以判断一个变量是否属于一个类型或者他的子类型
print(str is String); // true
// is! 跟 is 完全相反,当运算符 is! 左边的变量不属于右边的类型,或者不属于右边的子类型,才返回 ture
print(str is! String); // false
// 还可以用来判断左边是否是右边的子类
print(str is Object); // true

避空运算符 ​

dart
// 表示如果左边为空则取右边的值,和或运算符类似
print(null ?? 3); // 3

// 另一种用法
var a;
// if (a == null) {
//   a = 3;
// }
// 上面注释的代码可用避空运算符实现
a ??= 3;

?. 运算符 ​

dart
// 保护为空的属性,当不存在某一属性时不会去访问防止报错
var obj;
print(obj?.length); // null

级联运算符 ​

dart
// myobj.myMethod();返回的是 myMethod 方法的返回值
// myobj..myMethod();返回的是 myobj 对象的引用
Set s = new Set();
// s.add(1);
// s.add(2);
// s.remove(2);
// 下面写法利用级联运算符可实现上面一样的操作
s..add(1)..add(2)..remove(2);
print(s);

循环 ​

for 循环 ​

dart
for (int i = 0; i < 3; i++) {
    print(i);
}

for in 循环 ​

dart
List m = [1, 2, 3];
for (int item in m) {
    print(item);
}

while 循环 ​

dart
int m = 1;
while (m < 3) {
    print(m++);
}

do while 循环 ​

dart
// do while 循环会先执行 do 里面的代码再走 while 里的判断
int m = 1;
do {
    print(m++);
} while (m < 3);

函数 ​

dart
// void 表示该函数无返回值(默认返回null),函数也有自己的类型是 Function
void main() {
  print(sum(2, 3));
}

// 返回两数之和的函数,sum 也可以写在 main 函数体内,不过必须写在调用之前
int sum(int a, int b) {
  return a + b;
}

// 借助类型推断可以简写为如下形式,此时参数的类型是 dynamic 动态类型,什么类型都能接收
sum( a,  b) {
  return a + b;
}

// 定义可选参数和给可选参数指定默认值,这种写法在调用的时候需要以键值对的方式传入
sum(int a , int b,{int? c,String? d='2'}) {
  print(a + b);
}
// 这种写法在调用的时候需要以键值对的方式传入,这里的 c、d参数需要如下写法传入
sum(1, 2,c:2,d:'3');

// required 表示参数必填
fn(int a, int b, {int c = 9, required String d}) {
  print('a+b=$a+$b=$c+$d');
}
// 这种写法在调用的时候需要以键值对的方式传入,这里的 c、d参数需要如下写法传入
fn(1, 2, d: 'hello'); // a+b=1+2=9+hello

// 可选参数的另一种写法,这种写法表示 c 可选且默认值是 0
sum(int a, int b, [int c = 0]) {
    print(a + b);
}
sum(1, 2,3);

箭头函数 ​

dart
// 箭头函数的函数体里只能有一条语句
// 箭头函数仅仅只是普通函数的简写方式不同于 js 里的箭头函数有其他的作用
void main() {
  print(sum(2, 3));
}

sum(int a , int b) {
  return a+b;
}

// 上面函数可简写为如下箭头函数形式
sum(int a , int b) => a+b;

立即执行函数 ​

dart
((int n) {
    print(n);
})(17); // 17

函数类型 ​

dart
void main() {
    int add(int a, int b) {
        return a + b;
    }
    // 给函数执行类型
    int Function(int, int) fn = add;
    fn(1, 2);
}

作用域 ​

dart 中的作用域和 js 中一致

闭包 ​

dart 中闭包和 js 中一致

类 ​

基本使用 ​

在 Dart 中所有的类都继承自 object

dart
void main() {
    // 实例化
    Person p = Person('张三', 18);
    // 调用类方法
    p.printInfo();
}

// 自定义类只能编写在 main 函数之外
class Person {
    String name;
    int age;
    // 默认构造函数
    /*
    	Person(String name, int age) {
        	this.name = name;
        	this.age = age;
      	}
      	可简写为如下写法
    */
    Person(this.name, this.age);

    void printInfo() {
        print('姓名:${this.name},年龄:${this.age}');
    }

    // @override 表示重写父类的方法
    @override
    String toString() {
        return 'name:$name,age:$age';
    }
}

构造函数 ​

默认构造函数 ​

在实例化时就会执行

dart
void main() {
    Person p = Person();
}

class Person {
    Person() {
        print('这是构造函数里面的内容,这个方法在实例化的时候触发');
    }
}

命名构造函数 ​

dart
void main() {
  // Person p = Person(); 默认实例化类的时候调用的是默认构造函数
  // 如果想调用其他构造函数来创建实例化,可调用命名构造函数
  Person p = Person.withName('张三');
  print(p.age);
  print(p.jiao());
}

class Person {
  Person(this.name, this.age);
  // 命名构造函数可以有多个
  // Person.withName(String name){
  //   this.name = name;
  // }
  // 上面可简写为如下格式
  Person.withName(this.name);
  String name = '小名';
  num? age;
  String jiao() {
    return '${this.age}${this.name}';
  }
}

常量构造函数 ​

dart
// 如果类生成的对象不会改变,可以通过常量构造函数使这些对象成为编译时常量,可以提高性能
class Point {
  num x;
  Point(this.x);
}

class ImmutablePoint {
  // 属性必须通过 final 声明
  final x;
  // 常量构造函数必须通过 const 声明,且不能有函数体
  const ImmutablePoint(this.x);
}

void main() {
  // 创建实例对象时 new 关键字可以省略不写
  var p1 = new Point(1);
  var p2 = new Point(1);
  print(p1 == p2); // false

  // 声明不可变对象必须通过 const
  var p3 = const ImmutablePoint(1);
  var p4 = const ImmutablePoint(1);
  print(p3 == p4); // true

  // 常量构造函数可以当做普通构造函数使用
  var p5 = new ImmutablePoint(1);
  var p6 = new ImmutablePoint(1);
  print(p5 == p6); // false
}

工厂构造函数 ​

dart
// 不会直接创建对象,而是在构造函数内部通过代码来决定要创建的对象
void main() {
  // 使用工厂构造函数创建对象
  Person p = Person.withInfo('张晓', 19);
  print(p.age);
  print(p.jiao());
}

class Person {
  Person(this.name, this.age);
  Person.withName(this.name, this.age);
  // 工厂构造函数必须返回一个对象,可以是之前定义的类名构造函数
  factory Person.withInfo(String name, int age) {
    // 工厂函数内不能访问 this
    // 工厂函数不能被实例化
    // return Person.withName(name, age);
    return age < 0 ? Person.withName(name, 0) : Person.withName(name, age);
  }
  String name = '小名';
  num? age;
  String jiao() {
    return '${this.age}${this.name}';
  }
}

class Person {
  String name = '';
  static dynamic instance = null;
  // 工厂构造函数主要是用来避免多次实例化重复实例化出来多个对象,提高性能
  factory Person([String name = '刘宝']) {
    // 工厂构造函数内不能访问 this 如果想访问类中属性必须用 类.属性 访问
    // 工厂构造函数不能被实例化
    if (Person.instance == null) {
      Person.instance = new Person.newSelf(name);
    }
    return Person.instance;
  }
  // 命名构造函数
  Person.newSelf(this.name);
}

void main() {
  Person p = new Person('小米');
  print(p.name); // 小米
  Person p2 = new Person('小米2');
  print(p2.name); // 小米
}

私有属性和方法 ​

test.dart

dart
// 只有把类单独抽离出去私有属性和方法才起作用,写在一个文件中不起作用
class Dog {
  // 公有属性
  String name = '小名';
  // 私有属性 在名称前加上 _ 表示私有,私有属性只能在本类中使用
  int _age = 20;
  // 公有方法
  void jiao() {
    print('jiao');
  }

  // 私有方法
  void _run() {
    print('jiao');
  }
}

holle.dart

dart
// 导入其他文件
import 'test.dart';

void main() {
  Dog dog = Dog();
  dog.name = '大黄';
  dog.jiao();
  // dog.run(); 报错,无法访问私有属性或方法
}

getter 和 setter ​

dart
class Circle {
  final double PI = 3.14;
  num r;
  Circle(this.r);
  // 使用 get 声明的方法不能有小括号
  num get area {
    return this.PI * this.r * this.r;
  }

  set setR(value) {
    this.r = value;
  }
}

void main() {
  Circle c = new Circle(2);
  print(c.area); // 12.56
  c.setR = 3;
  print(c.area); // 28.259999999999998
}

初始化列表和重定向构造函数 ​

dart
class Rect {
  int height;
  int width;
  // Rect([int height = 2, int width = 2]) {
  //   this.height = height;
  //   this.width = width;
  //   print('${this.height} -- ${this.width}');
  // }
  // 初始化列表,上面的构造函数赋默认值写法可简写为如下代码
  Rect()
      : height = 4,
        width = 5 {
    print('${this.height} -- ${this.width}');
  }
}

void main() {
  Rect c = new Rect();
}
////////////////////////////////////////////////////

class Point {
  double x, y, z;
  Point(this.x, this.y, this.z);
  // 初始化列表特殊用法(重定向构造函数)
  // 这里表示如果值传递两个参数,就用 Point 构造函数去初始化值,这里的 this 代表类本身
  Point.towD(double x, double y) : this(x, y, 0);
}

void main() {
  Point c = new Point.towD(1, 2);
}

static ​

dart
// 通过 static 来声明静态成员,静态成员可以通过类名称直接访问(不需要实例化)
// 实例化是比较消耗性能的,声明静态成员可以提高程序性能
class Person {
  static String name = '张三';
  int age = 2;
  printInfo() {
    // 静态方法可以访问静态成员以及非静态成员
    // print(this.name); 不能通过 this 访问静态属性或方法
    // print(age); 静态方法中不能访问非静态属性以及方法
    print(name);
  }

  printInfo2() {
    // 非静态方法可以访问静态属性
    print(name);
  }
}

void main() {
  print(Person.name); // 静态成员可以直接通过类名访问
  // print(Person.printInfo2);  不能直接使用类名非访问静态方法或属性
}

元数据 ​

dart
// 元数据以@开头可以给代码标记一些额外的信息,仅作为一个标注和提示,对代码运行无影响
// @override(重写) 某方法添加该注解后表示重写了父类中同名方法

// @required(必填) 可以通过它来注解 Dart 中的命名参数,用来指示它是必填参数

// @deprecated(弃用) 若某类或方法加上此注解后,表示此方法或类不再建议使用
class Phone {
  // 这表示 activate 即将被弃用
  @deprecated
  activate(){
    turnOn();
  }
  turnOn(){
    print('开机');
  }
}

void main() {
  Phone p = Phone();
  p.activate();
}

继承 ​

dart
void main() {
    Zi z = Zi('张三', '男');
    print(z.name);
    z.jiao();
}

class Fu {
    String name;
    Fu(this.name);
    void jiao() {
        print('jiao$name');
    }
}

// dart 的继承是单继承,一个子类只能有一个父类
// 类使用 extends 关键词来继承父类
// 子类会继承父类里面可见的(除私有以及静态)属性和方法但是不会继承构造函数
class Zi extends Fu {
    String sex;
    String name;
    // 当父类初始化时需要参数,子类需要调用 super 将参数传递过去,也就是调用父类的构造函数
    Zi(this.name, this.sex) : super(name);
    // 子类可以重写父类的方法
    @override
    void jiao() {
        // 可以通过 super 关键字来调用父类的方法
        // super.jiao();
        print('子类重写父类的方法');
    }
}

抽象类 ​

dart
// 抽象类不能被实例化
// 通过 abstract 声明抽象类用来做类的模板
abstract class Phone {
  // 抽象类中可以没有抽象方法也可以有
  void processor(); // 声明抽象方法
  void camera(); // 声明抽象方法
  void info() {
    print('抽象类中可以有普通方法');
  }
}

class Xiaomi extends Phone {
  // 普通类中不能有抽象方法
  // 普通类继承抽象类必须实现抽象类中所有的抽象方法
  // 使用元数据标记这是一个重写的方法
  @override
  void processor() {
    print('骁龙888');
  }

  @override
  void camera() {
    print('三星');
  }
}

void main() {
  Xiaomi p = Xiaomi();
  p.processor();
}

接口 ​

dart
// 接口就是一个类,接口可以是任意类,但一般使用抽象类做接口
// 一个类可以实现(implements)多个接口
// 普通类实现接口后必须重写接口所有的属性和方法
// 使用抽象类来当做接口
abstract class Processor {
  late String cores;
  arch(String name);
}

abstract class Camera {
  late String resolution;
  brand(String name);
}

// 通过普通类来实现接口
class Phone implements Processor, Camera {
  // 实现属性
  String cores;
  String resolution;
  // 实现方法
  @override
  arch(String name) {
    print('实现接口');
  }

  @override
  brand(String name) {
    print('实现接口');
  }

  Phone(this.cores, this.resolution);
}

void main() {
  Phone p = Phone('2', '3');
  p.brand('3');
  p.arch('3');
}

mixin ​

dart
// 可以为类扩展功能,而不需要使用继承
// 可以扩展属性和方法
// 不能被实例化,不能被继承
// 一个类支持 with 多个 mixin,调用优先级遵循“后来居上”原则,即后混入的会覆盖先混入的同名方法
void main() {
  zi z = zi();
  s s1 = s();
  fu f = fu();
  z.walk();
  s1.walk();
  f.walk();
  print(f.sex);
}

// 定义 mixin
mixin WalkMixin {
  void walk() {
    print('walk');
  }
}
// 定义 mixin
mixin sexMixin {
  String sex = '男';
}

// 可以使用多个 mixin
class fu with WalkMixin, sexMixin {
  String name = '大黄';
  void jiao() {
    print('jiao');
  }
}

class s with WalkMixin {}

class zi extends fu with WalkMixin {
  void jiao() {
    print('jiao2');
  }
}

////////////////////////////////////////////////////////////////////////
// 通过类的方式声明混入,这种方式 MixinA 不能继承除 Object 之外的类,也不能有构造函数
class MixinA {
  String name = 'MixinA';
  printA() {
    print(name);
  }
}

// 通过 mixin 关键字声明混入
mixin MixinB {
  String name = 'MixinB';
  printB() {
    print(name);
  }
}

class Myclass with MixinB, MixinA {}

void main() {
  Myclass p = Myclass();
  // 使用多个混入时后引入的混入会覆盖之前混入中重复的内容
  print(p.name); // MixinA
  p.printA(); // MixinA
  p.printB(); // MixinA
}

异步编程 ​

Dart 是单线程语言,即同时只能做一件事,遇到耗时任务就会造成程序阻塞,此时需要异步编程。

Dart 采用单线程+事件循环的机制完成耗时任务的处理

事件循环:执行同步代码 => 执行微任务队列 => 执行事件队列 => 结束

微任务队列:Future.microtask()

事件队列:Future、Future.delayed()、I/O操作(文件、网络)等

Future ​

Future 代表一个异步操作的最终结果

有三种状态:Uncompleted(等待)、Completedwithavalue(成功)、Completedwithaerror(失败)

执行成功:不抛出异常就是成功状态

dart
void main() {
    Future f = Future(() {
        // 没有抛出异常那就是成功状态
        return "HelLo Flutter";
        // 抛出异常就是失败状态
        // throw Exception('出错了');
    });
    // then 中接收成功状态
    f.then((value) {
        print(value); // HelLo Flutter
    });
    f.catchError((error) {
        print('发生错误$error'); // 发生错误出错了
    });
}

如下代码打印顺序为:

开始执行 => 假装这是不能被阻塞的代码 => Future => 这是异步任务执行的结果

dart
import 'dart:io';

void main() {
    print('开始执行');
    // Future 可以用来解决异步编程问题,写在这里的耗时任务不会立即执行
    Future(() {
        print('Future');
        // 耗时任务执行的地方
        sleep(Duration(seconds: 5)); // 表示程序在这里会延时 5 秒,本身 sleep 是同步代码
        return '这是异步任务执行的结果';
    })
        .then(
        (value) => {
            // 这里表示异步任务执行完成就会执行这里,value 可以接收到异步任务返回的数据
            print(value),
        },
    )
        .catchError((error) {
            // 这里可以捕获异步任务的错误
            print(error);
        });
    print('假装这是不能被阻塞的代码');
}

链式调用 ​

Future 可以通过链式的方式连续得到异步的结果

在上一个 then 返回对象会在下一个 then 中接收

dart
// 如下代码会打印 task1-task2-task3
void main() {
    Future(() => "task1")
        .then((value) {
            // 上一个 then 返回对象会在下一个 then 中接收
            // 第二个任务
            return Future(() => "$value-task2");
        })
        .then((value) {
            // 第三个任务
            return Future(() => "$value-task3");
        })
        .then((value) {
            print(value); // task1-task2-task3
            // throw Exception("异常");
        })
        .catchError((error) {
            print("出现错误");
        });
}

async 简化 ​

除了通过 then/catchError 的方式,还可以通过 async/await 来实现异步编程

await 总是等到后面的 Future 执行成功,才执行下方逻辑,async 必须配套 await 出现

dart
void main() {
    test();
}

test() async {
    String res =
        await Future(() {
            return 'hello world';
            // throw Exception();
        }).catchError((onError) {
        // 在这里捕获错误
        print('出错了');
    });
    // await 下方的逻辑会在 Future 执行成功后执行
    print(res); // hello world
}

泛型 ​

使用泛型指定类型 ​

dart
void main() {
  // List arr = ['s',1,true]; 不指定泛型时 List 是可以存储任意类型的元素的
  List<String> arr = ['s', '2']; // 通过泛型指定 List 中保存指定类型
  // 不指定泛型时 Map 中 key 可以是任意类型
  // Map obj = {'id': 1, 'name': "居家", 1: 2, true: 1};
  // 使用泛型指定 Map 键和值的类型
  Map<String, String> obj = {'id': '1', 'name': "居家", '1': '2', 'true': '1'};
  // 还可以这样写
  List a = <int>[2,3];
  Map m = <String,String>{
    '2':''
  };
}

使用泛型简化代码 ​

dart
void main() {
    String s(String str) {
        return str;
    }

    int a(int str) {
        return str;
    }

    // 上面的两个函数可以利用泛型写成一个
    T fn<T>(T info) {
        return info;
    }
}

// 泛型类
class P<T> {
    getinfro(T s) {
        print(s);
    }
}

void main() {
    P p = P<String>();
    p.getinfro('2');
}

泛型类型限制 ​

dart
class Somether {}

class Anoth {}

// 限制参数类型必须继承 Somether 或 Somether 子类
class Foo<T extends Somether> {
  getinfo() {
    print(T);
  }
}

void main() {
  Foo f = new Foo<Somether>();
  f.getinfo();
}

异常 ​

dart
void main() {
  try {
    dynamic a = 'ss';
    a.haha();
  } catch (error) {
    print('出错');
  } finally {
    // 无论是否出现异常都会执行这里的代码
  }
  String s = 'zxc';
  if (s == 'zxc1') {
    print('ok');
  } else {
    // 手动抛出异常
    throw '字符串不相等';
  }
}

dart 库与生态 ​

dart
// https://pub.flutter-io.cn/ 对标js中的 npm

自定义库 ​

dart
// 每个 Dart 文件默认都是一个库,只是没有使用 library 来显示声明
// Dart 使用 _(下划线)开头的标识符,表示库内访问可见(私有)
// library 关键字声明的库名称建议使用: 小写字母 + 下划线

index.dart

dart
import './test.dart';

void main() {
  Dog m = new Dog();
  print(m.name);
}

test.dart

dart
// library 关键字声明的库名称建议使用: 小写字母 + 下划线
library my_dog;

class Dog {
  String name = '小名';
  void info() {
    print('自定义库');
  }
}

系统库 ​

dart
// import 'dart:系统库名称';
import 'dart:math';
// import 'dart:core'; // core 库即使不手动引入也会默认引入
void main() {
  print(pi); // 3.141592653589793   获取圆周率
  print(min(3, 6)); // 3
  print(max(3, 6)); // 6
}

按需引入 ​

dart
// show 表示只引入后续的内容(除了后续指定的内容,其他的都不引入)
import './test.dart' show f1, f3;
// hide 表示隐藏后面的内容(除了后续指定的内容,其他的都引入)
import './test.dart' hide f1, f3;

void main() {
  f2();
}

引入冲突 ​

dart
import './test.dart';
// 当多个包里的名称重名时使用 as 添加前缀解决
import './test2.dart' as test2;

void main() {
  f2();
  test2.f2();
}

test.dart

dart
f2() {
  print('f2');
}

test2.dart

dart
f2() {
  print('f2');
}

异步引入(懒加载) ​

dart
// 异步加载此时只是和该库建立联系并未真正引入
import './test.dart' deferred as func;

void main() {
  greet();
}

Future greet() async {
  // loadLibrary 函数返回一个 Future 表示等待库引入,这时可以使用 async 或者 then 来指定引入完成后要干的事
  await func.loadLibrary();
  func.f1();
}

主库和分库 ​

分库使用 part of 和主库建立联系,主库使用 part 和分库建立联系,最终组成一个完整的库