一、前言
本系列文章旨在快速复习并上手Flutter开发,并在适当分享在项目实战过程中遇到的一些比较有价值的知识内容:
- 01-📝Flutter核心知识|了解Flutter【诞生背景、应用简介、页面渲染原理、搭建开发环境、创建Flutter项目的几种方式】
- 02-📝Flutter核心知识|Dart语言快速入门|常规语法【Dart介绍和安装、程序分析、常用数据类型、函数、运算符、逻辑分支】
- 03-📝Flutter核心知识|Dart语言快速入门|面向对象【类和对象、泛型、库的使用、异步模型、异步操作】
- 04-📝Flutter核心知识|了解Widget【Center、MaterialApp、Row、CheckBox、DIY、Column、ListView等】
- 05-📝Flutter核心知识|常用Widget1【StatefulWidget、文本Widget、按钮Widget、图片Widget、表单Widget】
- 06-📝Flutter核心知识|布局与滚动组件【单子布局、多子布局、JSON读取和解析、ListView、GridView、Slivers、监听滚动事件
- 07-📝Flutter核心知识|自定义Widget【StarRating、DashedLine、底部TabBar】
本系列文章内容篇幅如下:
- 一、了解Flutter
-
- Flutter的特性与应用场景
-
- Flutter绘制原理
-
- 与Flutter相关的技术原理
-
- 搭建Flutter开发环境
-
- 创建Flutter项目的几种方式
-
- 二、快速入门Flutter开发知识大纲
-
- Dart语言快速入门
-
- Flutter的Widget
-
- 三、常见应用功能模块与开源项目
-
- 常见应用功能模块
-
- 不错的开源项目
-
二、Dart介绍和安装
1. 认识Dart
Dart语言是Google为Flutter选择的开发语言,所以我们要使用Flutter开发,需要从Dart开始
2. 安装Dart
我们在安装FlutterSDK的时候,SDK已经内置了Dart 开发包,所以这里省略
3. VSCode配置
上手Dart语法过程中,我使用VSCode
作为编辑器,原因如下:
- 编写代码非常方便,相较于
SublimeText
我更喜欢VSCode
- 可以快速在终端看我编写代码的效果
目前给VSCode安装、插件
使用VSCode
编写Dart需要安装Dart插件
:
Dart
和Flutter
插件是为Flutter开发准备的Atom One Dark Theme
是我个人比较喜欢的一个主题Code Runner
可以点击右上角的按钮让我快速运行代码
三、 Hello Dart
1. Hello World
按照以往学习编程语言的惯例,我们从Hello World
开始。
在VSCode中新建一个 helloWorld.dart
文件,添加下面的内容:
main(List<String> args) {
print('Hello World');
}
然后在终端执行dart helloWorld.dart
,就能看到 Hello World
的结果了。
完成了这个执行过程之后,以你之前学习的编程语言来看,你能得到多少信息呢?
2. 程序分析
- Dart语言的入口也是
main
函数,并且必须显示的进行定义; - Dart的入口函数
main
是没有返回值的; - 传递给
main
的命令行参数,是通过List<String>
完成的;- 从字面值就可以理解
List
是Dart中的集合类型。 - 其中的每一个
String
都表示传递给main
的一个参数;
- 从字面值就可以理解
定义字符串
的时候,可以使用单引号
或双引号
;- 每行语句必须使用
分号结尾
,很多语言并不需要分号,比如Swift、JavaScript;
四、 定义变量
1. 明确声明(Explicit)
- 明确声明变量的方式, 格式如下:
变量类型 变量名称 = 赋值;
- 示例代码:
String name = 'VanZhang'; int age = 18; double height = 1.88; print('${name}, ${age}, ${height}'); // 拼接方式后续会讲解
- 注意事项: 定义的变量可以修改值, 但是
不能赋值其他类型
String content = 'Hello Dart'; content = 'Hello World'; // 正确的 content = 111; // 错误的, 将一个int值赋值给一个String变量
2. 类型推导(Type Inference)
类型推导
声明变量 的方式, 格式如下:
var/dynamic/const/final 变量名称 = 赋值;
3. var的使用
var的使用示例:
- runtimeType用于获取变量当前的类型
var name = 'VanZhang'; name = 'kobe'; print(name.runtimeType); // String
- var的错误用法:
var age = 18; age = 'hello'; // 不可以将String赋值给一个int类型
4. dynamic的使用
如果确实希望这样做,可以使用dynamic来声明变量:
- 但是在开发中, 通常情况下不使用
dynamic
, 因为类型的变量会带来潜在的危险dynamic name = 'VanZhang'; print(name.runtimeType); // String name = 18; print(name.runtimeType); // int
5. final&const的使用
final
和const
都是用于定义常量的, 也就是定义之后值都不可以修改final name = 'VanZhang'; name = 'kobe'; // 错误做法 const age = 18; age = 20; // 错误做法
final
和const
有什么区别呢?const
在赋值时, 赋值的内容必须是在编译期间就确定下来的final
在赋值时, 可以动态获取, 比如赋值一个函数
String getName() { return 'VanZhang'; } main(List<String> args) { const name = getName(); // 错误的做法, 因为要执行函数才能获取到值 final name = getName(); // 正确的做法 }
final
和const
小案例:- 首先,
const
是不可以赋值为DateTime.now()
不可以以函数的计算结果赋值 - 其次,
final
一旦被赋值后就有确定的结果, 不会再次赋值
// const time = DateTime.now(); // 错误的赋值方式 final time = DateTime.now(); print(time); // 2019-04-05 09:02:54.052626 sleep(Duration(seconds: 2)); print(time); // 2019-04-05 09:02:54.052626
- 首先,
const
放在赋值语句的右边,可以共享对象,提高性能:
暂时先做了解,在类的常量构造函数篇幅,会进一步深入分析class Person { const Person(); } main(List<String> args) { final a = const Person(); final b = const Person(); print(identical(a, b)); // true final m = Person(); final n = Person(); print(identical(m, n)); // false }
五、 常用数据类型
1. 数字类型
- 对于数值的使用,我们不用关心它是否有符号,以及数据的宽度和精度等问题
- 只要记着整数用
int
,浮点数用double
就行了 - 注意:
Dart
中的int
和double
可表示的范围并不是固定的- 它取决于运行Dart的平台。
// 1.整数类型int int age = 18; int hexAge = 0x12; print(age); print(hexAge); // 2.浮点类型double double height = 1.88; print(height);
- 字符串和数字之间的转化:
// 字符串和数字转化 // 1.字符串转数字 var one = int.parse('111'); var two = double.parse('12.22'); print('${one} ${one.runtimeType}'); // 111 int print('${two} ${two.runtimeType}'); // 12.22 double // 2.数字转字符串 var num1 = 123; var num2 = 123.456; var num1Str = num1.toString(); var num2Str = num2.toString(); var num2StrD = num2.toStringAsFixed(2); // 保留两位小数 print('${num1Str} ${num1Str.runtimeType}'); // 123 String print('${num2Str} ${num2Str.runtimeType}'); // 123.456 String print('${num2StrD} ${num2StrD.runtimeType}'); // 123.46 String
2. 布尔类型
- 布尔类型中,Dart提供了一个bool的类型, 取值为
true
和false
// 布尔类型 var isFlag = true; print('$isFlag ${isFlag.runtimeType}');
- 注意:
- Dart中不能判断
非0即真
, 或者非空即真
- dart的类型安全性意味着您不能使用if(非booleanvalue)或assert(非booleanvalue)之类的代码
var message = 'Hello Dart'; // 错误的写法 if (message) { print(message) }
- Dart中不能判断
3. 字符串类型
- dart字符串是
UTF-16
编码单元的序列。您可以使用单引号或双引号创建一个字符串:// 1.定义字符串的方式 var s1 = 'Hello World'; var s2 = "Hello Dart"; var s3 = 'Hello'Fullter'; var s4 = "Hello'Fullter";
- 可以使用三个单引号或者双引号表示多行字符串:
// 2.表示多行字符串的方式 var message1 = ''' 哈哈哈 呵呵呵 嘿嘿嘿''';
- 字符串和其他变量或表达式拼接: 使用${expression}, 如果表达式是一个标识符, 那么{}可以省略
// 3.拼接其他变量 var name = 'VanZhang'; var age = 18; var height = 1.88; print('my name is ${name}, age is $age, height is $height');
4. 集合类型
4.1. 集合类型的定义
对于集合类型,Dart则内置了最常用的三种:List / Set / Map
:
List
可以这样来定义:// List定义 // 1.使用类型推导定义 var letters = ['a', 'b', 'c', 'd']; print('$letters ${letters.runtimeType}'); // 2.明确指定类型 List<int> numbers = [1, 2, 3, 4]; print('$numbers ${numbers.runtimeType}');
set
可以这样来定义:- 其实,也就是把
[]
换成{}
就好了。 Set
和List
最大的两个不同就是:Set
是无序的,并且元素是不重复的。
// Set的定义 // 1.使用类型推导定义 var lettersSet = {'a', 'b', 'c', 'd'}; print('$lettersSet ${lettersSet.runtimeType}'); // 2.明确指定类型 Set<int> numbersSet = {1, 2, 3, 4}; print('$numbersSet ${numbersSet.runtimeType}');
- 其实,也就是把
Map
是我们常说的字典类型,它的定义是这样的:// Map的定义 // 1.使用类型推导定义 var infoMap1 = {'name': 'hello', 'age': 18}; print('$infoMap1 ${infoMap1.runtimeType}'); // 2.明确指定类型 Map<String, Object> infoMap2 = {'height': 1.88, 'address': '北京市'}; print('$infoMap2 ${infoMap2.runtimeType}');
4.2. 集合的常见操作
了解了这三个集合的定义方式之后,我们来看一些最基础的公共操作
- 所有集合类型都支持的获取长度的属性
length
:// 获取集合的长度 print(letters.length); print(lettersSet.length); print(infoMap1.length);
添加
/删除
/包含
操作- 并且,对
List
来说,由于元素是有序的,它还提供了一个删除指定索引位置上元素的方法
// 添加/删除/包含元素 numbers.add(5); numbersSet.add(5); print('$numbers $numbersSet'); numbers.remove(1); numbersSet.remove(1); print('$numbers $numbersSet'); print(numbers.contains(2)); print(numbersSet.contains(2)); // List根据index删除元素 numbers.removeAt(3); print('$numbers');
- 并且,对
Map
独有的操作- 由于它有key和value,因此无论是读取值,还是操作,都要明确是基于key的,还是基于value的,或者是基于key/value对的。
// Map的操作 // 1.根据key获取value print(infoMap1['name']); // hello // 2.获取所有的entries print('${infoMap1.entries} ${infoMap1.entries.runtimeType}'); // (MapEntry(name: hello), MapEntry(age: 18)) MappedIterable<String, MapEntry<String, Object>> // 3.获取所有的keys print('${infoMap1.keys} ${infoMap1.keys.runtimeType}'); // (name, age) _CompactIterable<String> // 4.获取所有的values print('${infoMap1.values} ${infoMap1.values.runtimeType}'); // (hello, 18) _CompactIterable<Object> // 5.判断是否包含某个key或者value print('${infoMap1.containsKey('age')} ${infoMap1.containsValue(18)}'); // true true // 6.根据key删除元素 infoMap1.remove('age'); print('${infoMap1}'); // {name: hello}
六、 函数
1. 函数的基本定义
- dart是一种
面向对象语言
,它的函数也可以当做对象处理,所以函数也有类型, 类型就是Function - 这意味着
- 函数可以作为变量定义
- 函数可以作为其他函数的参数或者返回值.
- 函数的定义方式:
返回值 函数的名称(参数列表) { 函数体 return 返回值 }
- 示例:
int sum(num num1, num num2) { return num1 + num2; }
- 示例:
Effective Dart
建议对公共的API, 使用类型注解
虽然如果我们省略掉了类型, 依然是可以正常工作,但这类编码风格不够友好通用,不能一看便知sum(num1, num2) { return num1 + num2; }
- 另外, 如果函数中只有一个表达式, 那么可以使用箭头语法(
arrow syntax
)
注意, 这里面只能是一个表达式, 不能是一个语句sum(num1, num2) => num1 + num2;
2. 函数的参数问题
函数的参数可以分成两类: 必须参数
和可选参数
前面使用的参数都是必须参数.
2.1. 可选参数
可选参数可以分为 命名可选参数 和 位置可选参数
定义方式:
命名可选参数: {param1, param2, ...}
位置可选参数: [param1, param2, ...]
命名可选参数的演示:
// 命名可选参数
printInfo1(String name, {int age, double height}) {
print('name=$name age=$age height=$height');
}
// 调用printInfo1函数
printInfo1('hello'); // name=hello age=null height=null
printInfo1('hello', age: 18); // name=hello age=18 height=null
printInfo1('hello', age: 18, height: 1.88); // name=hello age=18 height=1.88
printInfo1('hello', height: 1.88); // name=hello age=null height=1.88
位置可选参数的演示:
// 定义位置可选参数
printInfo2(String name, [int age, double height]) {
print('name=$name age=$age height=$height');
}
// 调用printInfo2函数
printInfo2('hello'); // name=hello age=null height=null
printInfo2('hello', 18); // name=hello age=18 height=null
printInfo2('hello', 18, 1.88); // name=hello age=18 height=1.88
命名可选参数, 可以指定某个参数是必传的(使用@required, 有问题)
// 命名可选参数的必须
printInfo3(String name, {int age, double height, @required String address}) {
print('name=$name age=$age height=$height address=$address');
}
2.2. 参数默认值
参数可以有默认值, 在不传入的情况下, 使用默认值
- 注意, 只有可选参数才可以有默认值, 必须参数不能有默认值
// 参数的默认值
printInfo4(String name, {int age = 18, double height=1.88}) {
print('name=$name age=$age height=$height');
}
dart中的main函数就是一个接受可选的列表参数作为参数的, 所以在使用main函数时, 我们可以传入参数, 也可以不传入
3. 函数很具备灵活性
在Dart语言中的函数很具备灵活性;
- 在很多语言中, 比如Java/OC. 函数都有一定的使用限制,这让编程不够灵活, 所以现代的编程语言基本都支持提高函数的使用灵活性(如Swift), Dart也支持.
- 这就意味着我们可以将函数赋值给一个变量
- 也可以将函数作为另外一个函数的参数或者返回值来使用.
main(List<String> args) {
// 1.将函数赋值给一个变量
var bar = foo;
print(bar);
// 2.将函数作为另一个函数的参数
test(foo);
// 3.将函数作为另一个函数的返回值
var func =getFunc();
func('kobe');
}
// 1.定义一个函数
foo(String name) {
print('传入的name:$name');
}
// 2.将函数作为另外一个函数的参数
test(Function func) {
func('VanZhang');
}
// 3.将函数作为另一个函数的返回值
getFunc() {
return foo;
}
4. 匿名函数的使用
大部分我们定义的函数都会有自己的名字, 比如前面定义的foo、test函数等等。
但是某些情况下,给函数命名太麻烦了,我们可以使用没有名字的函数,这种函数可以被称之为匿名函数(anonymous function
),也可以叫 lambda
或者 closure
。
main(List<String> args) {
// 1.定义数组
var movies = ['盗梦空间', '星际穿越', '少年派', '大话西游'];
// 2.使用forEach遍历: 有名字的函数
printElement(item) {
print(item);
}
movies.forEach(printElement);
// 3.使用forEach遍历: 匿名函数
movies.forEach((item) {
print(item);
});
movies.forEach((item) => print(item));
}
5. 词法作用域
dart中的词法有自己明确的作用域范围,它是根据代码的结构({})来决定作用域范围的
优先使用自己作用域中的变量,如果没有找到,则一层层向外查找。
var name = 'global';
main(List<String> args) {
// var name = 'main';
void foo() {
// var name = 'foo';
print(name);
}
foo();
}
6. 词法闭包
闭包可以访问其词法范围内的变量,即使函数在其他地方被使用,也可以正常的访问。
main(List<String> args) {
makeAdder(num addBy) {
return (num i) {
return i + addBy;
};
}
var adder2 = makeAdder(2);
print(adder2(10)); // 12
print(adder2(6)); // 8
var adder5 = makeAdder(5);
print(adder5(10)); // 15
print(adder5(6)); // 11
}
7. 返回值问题
所有函数都返回一个值。如果没有指定返回值,则语句返回null;隐式附加到函数体。
main(List<String> args) {
print(foo()); // null
}
foo() {
print('foo function');
}
七、 运算符
这里,我只列出来相对其他语言比较特殊的运算符,因为某些运算符太简单了,不浪费时间,比如+、-、+=、==。
你可能会疑惑,Dart为什么要搞出这么多特殊的运算符呢?
你要坚信一点:所有这些特殊的运算符都是为了让我们在开发中可以更加方便的操作,而不是让我们的编码变得更加复杂。
1. 除法、整除、取模运算
我们来看一下除法、整除、取模运算
var num = 7;
print(num / 3); // 除法操作, 结果2.3333..
print(num ~/ 3); // 整除操作, 结果2;
print(num % 3); // 取模操作, 结果1;
2. ??=赋值操作
dart有一个很多语言都不具备的赋值运算符:
- 当变量为null时,使用后面的内容进行赋值。
- 当变量有值时,使用自己原来的值。
main(List<String> args) {
var name1 = 'coderwhy';
print(name1);
// var name2 = 'kobe';
var name2 = null;
name2 ??= 'james';
print(name2); // 当name2初始化为kobe时,结果为kobe,当初始化为null时,赋值了james
}
3. 条件运算符:
dart中包含一直比较特殊的条件运算符:expr1 ?? expr2
- 如果expr1是null,则返回expr2的结果;
- 如果expr1不是null,直接使用expr1的结果。
var temp = 'why';
var temp = null;
var name = temp ?? 'kobe';
print(name);
4. 级联语法:..
- 某些时候,我们希望对一个对象进行连续的操作,这个时候可以使用级联语法
class Person {
String name;
void run() {
print("${name} is running");
}
void eat() {
print("${name} is eating");
}
void swim() {
print("${name} is swimming");
}
}
main(List<String> args) {
final p1 = Person();
p1.name = 'why';
p1.run();
p1.eat();
p1.swim();
final p2 = Person()
..name = "why"
..run()
..eat()
..swim();
}
八、逻辑分支
和大部分语言的特性比较相似,这里就不再详细赘述,看一下即可。
1. if和else
和其他语言用法一样
这里有一个注意点:不支持非空即真或者非0即真,必须有明确的bool类型
- 我们来看下面name为null的判断
2. 循环操作
基本的for循环
for (var i = 0; i < 5; i++) {
print(i);
}
for in遍历List和Set类型
var names = ['why', 'kobe', 'curry'];
for (var name in names) {
print(name);
}
while和do-while和其他语言一致
break和continue用法也是一致
3. switch-case
普通的switch使用
- 注意:每一个case语句,默认情况下必须以一个break结尾
main(List<String> args) {
var direction = 'east';
switch (direction) {
case 'east':
print('东面');
break;
case 'south':
print('南面');
break;
case 'west':
print('西面');
break;
case 'north':
print('北面');
break;
default:
print('其他方向');
}
}