Dart 是学习Flutter的前提条件,因此掌握一些Dart基础知识是必要的。Dart是一门强类型语言,和Java类型,而又和KotlinSwift等语言也很相似。

搭建Dart开发环境

因为最终是需要开发Flutter应用,因此开发工具是AndroidStudio,当然也可以使用Visual Studio Code,它也可以开发Flutter应用。

首先需要安装Dart插件:

File -> Settings -> Plugins -> ‘输入Dart’ -> 点击Install按钮安装

dart和android sdk的版本 dart开发安卓_Dart

安装完重启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语言中有finalconst两个关键字来修饰常量。常量为声明后不可改变的量,而在声明时必须要初始化。

constfinal有相同的地方也有不同的地方。

其实const才算真正意义上的常量,const只能修饰编译器就被确定的字面常量,如String int 等,而final可修饰对象,而对象中的属性值可以改变。

constfinal共同点

void main() {
    // 下面2行等价,修饰字面常量
	const [String] name = "Jackson";
    final [String] name = "Jackson";
}

constfinal不同点

// 在方法中
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修饰

总结:

  • constfinal都可修饰常量,但final可修饰对象而const不可以;
  • 顶层属性和方法内局部属性都不可用static修饰,类中的const必要要用static修饰。
数据类型
数字类型(num ,int ,double)

表示数字类型的有 num int double三种,intdoublenum的子类。

  • 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来声明一个布尔类型,布尔类型只有truefalse两种值,布尔值一般都是作为判断条件的真或假。

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 ifelse根据场景选择使用。

  • 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
    continuebreak都可以表示打断循环,但是使用场景有点不同。
  • 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.namethis.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() {} // 这是一个普通方法
}