Dart
是学习Flutter
的前提条件,因此掌握一些Dart
基础知识是必要的。Dart是一门强类型语言,和Java
类型,而又和Kotlin
、Swift
等语言也很相似。
搭建Dart开发环境
因为最终是需要开发Flutter
应用,因此开发工具是AndroidStudio
,当然也可以使用Visual Studio Code
,它也可以开发Flutter
应用。
首先需要安装Dart
插件:
File
-> Settings
-> Plugins
-> ‘输入Dart’ -> 点击Install按钮安装
安装完重启AndroidStudio
就完成了环境的搭建。
变量
用var
关键字来声明一个变量,变量顾名思义是定义后还可以改变的量。变量的声明格式为:
var 变量名 [= 值]
or
变量类型 变量名 [= 值]
[]
表示可选,因此定义变量时,不强制马上赋值,可以在使用前再赋值。
注意:var关键字不能与变量类型同时存在,定义变量时使用var就不能显示指定变量类型,不使用var就必须指明类型。
使用var声明变量,最好马上赋初始值,因为var声明的变量的值可以再次改变,而且没有指定类型,就会引起安全隐患。
例如:
var
声明并赋初始值
var name = "Jackson"; // 编译通过
name = 10; // 编译错误,name在声明时赋了初始值,就在编译器确定了类型为String,再次赋值为int型报错
var
和变量类型同时存在
String name = "Jackson"; // 编译通过
var String name = "Jackson"; // 编译错误,var与String不能同时存在,因为根据Jackson就可以确定类型
使用String
指明类型且不赋初始值
String name;
name = "Jackson"; // 编译通过
print(name); // 打印 Jackson
name = 10; // 编译错误,类型确定为String,不能再次赋值为int类型
使用var
不赋初始值
var name;
name = "Jackson"; // 编译通过
print(name); // 打印 Jackson
name = 10; // 编译通过
print(name); // 打印 10
通过以上几个案例可以总结出:
- 使用
var
时,应该要赋一个初始值来确定变量类型,避免再次修改赋值类型不一致; - 不使用
var
时,因为指明了类型,因此可以不用赋初始值。
常量
Dart
语言中有final
和const
两个关键字来修饰常量。常量为声明后不可改变的量,而在声明时必须要初始化。
const
和final
有相同的地方也有不同的地方。
其实const
才算真正意义上的常量,const
只能修饰编译器就被确定的字面常量,如String
int
等,而final
可修饰对象,而对象中的属性值可以改变。
const
和 final
共同点
void main() {
// 下面2行等价,修饰字面常量
const [String] name = "Jackson";
final [String] name = "Jackson";
}
const
和 final
不同点
// 在方法中
void main() {
// final可修饰对象而const不可以
final User user = User("Jackson", 20); // 编译通过
user._name = "Bob"; // 编译通过
print(user._name); // 打印 Bob
const User user = User("Jackson", 20); // 编译错误,const不能修饰对象
}
// 在类中
class User{
// 在类中const必要要和static搭配使用,而final则不用
String _name;
int _age;
User(this._name, this._age);
final String a = "A"; // 编译通过
const String b = "B"; // 编译错误,class中const必须要用static修饰
static final String c = "C"; // 编译错误,final不可用static修饰
static const String d = "D"; // 编译通过
}
// 在方法和类外,和kotlin中的顶层函数与顶层属性位置一样,暂且业叫它为顶层属性吧
const String a = "A"; // 编译通过
static String b = "B"; // 编译错误
final String = c = "C"; // 编译通过
static final String d = "D"; // 编译错误
// 和在方法中的使用方式一致,均不能用static修饰
总结:
-
const
和final
都可修饰常量,但final
可修饰对象而const
不可以; - 顶层属性和方法内局部属性都不可用
static
修饰,类中的const
必要要用static
修饰。
数据类型
数字类型(num ,int ,double)
表示数字类型的有 num
int
double
三种,int
和double
是num
的子类。
- int
整数
const int i = 20;
- double
小数
const double d = 20.0;
const double e = 20;
print(e); // 打印20.0,会将int型转成double型
- num
包含整数和小数
const num a = 10;
const num b = 10.0;
print("a = $a , b = $b"); // 打印 a = 10 , b = 10.0
print(a is int); // 打印 true
print(b is double); // 打印 true
字符串(String)
字符串是一组字符序列,用单引号 ``
或者 双引号 ""
表示,引号之间就是字符串的内容。
声明字符串:
String str = "hello";
字符串拼接:
String hw = str + "world"; // 使用加号拼接字符串
print(hw); // 打印 helloworld
字符串原格式输出:
使用三对单引号或者双引号来保持字符串的格式
var s = """this is the first line
this is the second line
this is the third line with space.
""";
print(s);
// 打印
this is the first line
this is the second line
this is the third line with space.
字符串原始字符输出:
在字符串加上r
前缀,可以让字符串保持原始的字符,即让可转义的字符不转义。
var r1 = "hello\nworld";
print(r1);
// 打印
hello
world
var r2 = r"hello\nworld";
print(r2);
// 打印
hello\nworld
布尔类型(bool)
使用bool
来声明一个布尔类型,布尔类型只有true
和false
两种值,布尔值一般都是作为判断条件的真或假。
bool f = false;
bool t = true;
// 只有当condition为true时,才会执行{}代码块中的代码
if (condition) {}
while (condition) {}
集合(List,Set,Map)
- List
List表示一组有序且元素可重复的集合。
定义一个集合常量
const List list = [1, 1, 2, 3, 4];
print(list); // 打印 [1, 1, 2, 3, 4]
动态添加元素
List list = new List(); // 创建一个集合对象
list.add(1);
list.add(2);
print(list); // 打印 [1, 2]
获取元素
const List list = [1, 1, 2, 3, 4];
print(list[0]); // 使用中括号加索引的方式获取,打印 1
print(list.elementAt(4)); // 打印 4
修改元素
List list = [1, 1, 2, 3, 4];
list[0] = 10;
print(list); // [10, 1, 2, 3, 4]
删除元素
List list = [1, 1, 2, 3, 4];
list.remove(1); // 根据元素值删除,当有多个重复元素时,删除第一个
print(list); // 打印 [1, 2, 3, 4]
list.removeAt(0); // 根据元素索引删除
print(list); // 打印 [2, 3, 4]
- Set
Set表示一组无序且元素不可重复的集合。
定义一个集合常量,与List不同的是,定义Set常量不用[]
,而是用{}
。
const Set set = {1, 1, 2, 3, 4}; // 编译不通过,元素1重复
const Set set = {1, 2, 3, 4}; // 编译通过
print(set); // 打印 {1, 2, 3, 4}
动态添加元素
Set set = new Set(); // 创建一个集合对象
set.add(1);
set.add(2);
print(set); // 打印 {1, 2}
删除元素
Set set = {"Apple", "Banana", "Watermelon", "Peach"};
set.remove("Apple"); // 根据元素值删除
print(list); // 打印 {Banana, Watermelon, Peach}
- Map
Map表示key
-value
键值对,key
不可重复,value
可重复。把所有的key
放一起可看成一个Set
,把所有的value
放一起可看成一个List
。
定义一个Map,记录每种水果有多少个:
Map map = Map();
map["Apples"] = 10;
map["Bananas"] = 11;
map["Watermelons"] = 5;
map["Peaches"] = 8;
print(map); // 打印 {Apples: 10, Bananas: 11, Watermelons: 5, Peaches: 8}
访问元素(查看现有苹果多少个):
int apples = map["Apples"];
print(apples); // 10
修改元素(吃掉一个苹果):
map["Peaches"] = 9;
print(map); // 打印 {Apples: 10, Bananas: 11, Watermelons: 5, Peaches: 9}
删除元素(苹果吃完了):
map.remove("Apples");
print(map); // 打印 {Bananas: 11, Watermelons: 5, Peaches: 9}
泛型
用数据类型<类型>
来定义一个泛型,如:List<Strng>
。使用泛型提高了代码的质量。在使用泛型之前,定义一个List
,该List
可以添加任意类型的元素,但是在使用了泛型之后,只能往该List
中添加指定类型的元素,避免了从List
中获取数据但是不知道其具体类型而发生的类型转换错误。
不仅是变量能定义成泛型,还可以定义泛型类,泛型接口等。
List<String> list = List(); // List中只能添加String类型的元素
list.add("Apple");
list.add(1); // 编译错误,不允许增加int类型数据
控制语句
- 循环
假设需要输出10次hello world
,繁琐的办法是写10次print("hello world");
,但这显然不是一个好办法。这应该使用循环语句循环执行10遍``print(“hello world”);`
循环执行某个任务,直到满足终止条件。
- for
for(int i = 0;i < 10; i++) {
print("hello world");
}
- while
int i = 0;
while(i < 10) {
print("hello world");
i++;
}
- do-while
do-while
会先执行一次代码块中的代码,也就是不管满不满足while
中的条件都会先打印一次hello world
,然后再判断条件是否要继续执行。
int i = 0;
do {
print("hello world");
i++;
} while(i < 10);
- 分支
- if - else
一个完整的语句是这样的:
if (condition1) {
// 当condition1 = true时才能执行到这里
} else if (condition2) {
// 当condition2 = true时才能执行到这里
} else {
// 当condition1 和 condition2 都为false时执行到这里
}
一个分支语句if
是必须的,else if
和else
根据场景选择使用。
- switch - case
switch
语句可比较整型(不可比较double
型,因为double
类型不能重写==
操作符,所以switch
只能比较可重写==
操作符的类型)、字符串、枚举和同一个实例的对象等,匹配成功则执行case
中的语句,否则执行default
中的语句。
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
void switchFunc(Season season) {
switch (season) {
case Season.SPRING:
print("现在是春天");
break;
case Season.SUMMER:
print("现在是夏天");
return;
case Season.AUTUMN:
print("现在是秋天");
break;
case Season.WINTER:
print("现在是冬天");
break;
default:
print("case语句都没匹配到");
}
}
// 调用
switchFunc(Season.SUMMER); // 打印 现在是夏天
- continue / break
continue
和break
都可以表示打断循环,但是使用场景有点不同。
- continue - 中断当前循环,继续下次循环
for (int i = 0; i < 10; i++) {
if (i == 4) {
continue;
}
print(i); // 依次输出 0 1 2 3 5 6 7 8 9
}
- break - 中断全部循环,不再继续执行
for (int i = 0; i < 10; i++) {
if (i == 4) {
break;
}
print(i); // 依次输出 0 1 2 3
}
函数
- 函数定义
函数是对一个功能块的封装,它的类型是Function
,函数的定义格式是:
[返回值类型] 函数名(参数列表){代码块}
,当代码块中的代码只有一行时,可以省去{}
,直接使用=>
。
void hi(String who) {
print("Hello , $who");
}
// 等价于
hi(String who) => print("Hello , $who");
// 调用
hi("Allen");
// 打印 Hello , Allen
- 参数列表
参数是方法名后面一对小括号中间的变量,定义格式是参数类型 变量名
,如上面函数中的String who
,多个参数用逗号分开。 - 可选参数
当一个函数定义了很多参数时,有些参数可能并不是必须的,可以把这些不必须的参数设置成可选参数,可选参数分为命名可选参数
和位置可选参数
。命名可选参数
因为在传参的时候就标明了参数的含义,那么传递参数的顺序就不重要了,但是位置可选参数
的调用就需要参数一一对应了。
- 命名可选参数
用一对大括号{}
包裹的参数就是命名可选参数,在调用的时候用参数名:参数值
的形式传参。
// 方法中的参数只作为演示赋值,所以直接调用不全部传参的话会报null错
void printInfo({String name, int age, int height, bool isBoy}) {
print("My name is $name, i am $age years old, my height is $height and i am a ${isBoy?"boy":"girl"}");
}
// 调用,以下两种调用方式完全等价
printInfo(name: "Allen", age: 20, height: 180, isBoy: false);
printInfo(age: 20, name: "Allen", isBoy: false, height: 180);
可选参数中如果有参数是想必须要传的,***在Flutter中***可以用@required
修饰,假设名字name
是必传的:
void printInfo({@required String name, int age, int height, bool isBoy}) {
print("My name is $name , i am $age years old ,my height is $height and i am a ${isBoy?"boy":"girl"}");
}
没有使用@required
修饰的参数可以赋一个默认值,只有没有使用@required
修饰的参数才可以设置默认值:
void printInfo({@required String name, int age = 20, int height = 180, bool isBoy = true}) {
print("My name is $name , i am $age years old ,my height is $height and i am a ${isBoy?"boy":"girl"}");
}
- 位置可选参数
用一对中括号[]
来定义位置可选参数。
void printInfo([String name, int age = 20, int height = 180, bool isBoy = true]) {
print("My name is $name , i am $age years old ,my height is $height and i am a ${isBoy?"boy":"girl"}");
}
类
- 类声明
类声明用关键字calss
+类名
+{}
class Person {}
- 构造函数
class Person {
Person(); // 无参构造
}
class Person {
final String name;
final int age;
Person(this.name, this.age); // 有参构造
}
上面有参构造函数中使用this.name
和this.age
,这是一种简洁的写法,它等价于
class Person {
final String name;
final int age;
Person(this.name, this.age) {
this.name = name;
this.age = age;
}
}
- 获取类实例
Person p1 = Person("Allen", 20);
var p2 = Person("Bob", 21);
final p3 = new Person("Alice", 23);
以上三种获取类实例的方式都是合法的。
- 继承
继承用extends
关键字。
class Student extends Person {
Student(String name, int age) : super(name, age);
}
类只能单继承,如果要实现多继承的效果,需要使用混合的方式,关键字用with
。
// 增加一个Bird类,说明鸟有吃的能力
class Bird {
void eat() {
print("can eat");
}
}
// 重新定义一个学生类,学生属于人Person,也具备吃的能力
class Student extends Person with Bird {
Student(String name, int age) : super(name, age);
}
// 获取Student实例
Student student = Student("Bob", 20);
student.eat(); // student拥有了Bird类的eat方法,打印 can eat
当Student
类中和Bird
类中同时拥有eat
方法会怎么样呢?
class Student extends Person with Bird {
Student(String name, int age) : super(name, age);
void eat() {
print("$name can eat");
}
}
// 获取Student实例
Student student = Student("Bob", 20);
student.eat(); // 打印 Bob can eat,覆盖了Bird类中的eat方法
- 抽象类
抽象类用abstract
关键字修饰,抽象类中有0个或多个抽象方法,与Java
不同的是,Dart
中定义抽象方法不用abstract
关键字修饰,而是类似定义普通方法那样,只是不写方法体。
abstract class Person {
void eat(); // 这是一个抽象方法
void run() {} // 这是一个普通方法
}