Dart
目录
- 1 简介
- 2 变量、长量、命名规则
- 3 常用数据类型
- 4 字符串拼接
- 5 数据类型判断
- 6 循环语句,break 和 continue
- 7 List、Set、Map
- 8 函数
- 9 Dart 中的对象和类
- 10 Dart 中的类、静态成员、操作符、类的继承
- 11 Dart 中的抽象类、多态、以及接口
- 12 Dart 中一个类实现多个接口,以及 Dart 中的 Mixins
- 13 Dart 中的泛型,泛型方法、泛型类、泛型接口
- 14 Dart 中的库,自定义库、系统库、第三方库
- 15 Dart 和 Flutter 代码格式化
- 16 枚举 enum
1 简介
Dart 是由谷歌开发的计算机编程语言
,它可以被用于 web、服务器、移动应用 和物联网等领域的开发。
Dart 诞生于2011年,号称要取代 JavaScript。但是过去的几年中一直不温不火。直到 Flutter 的出现现在被人们重新重视。
要学 Flutter 的话我们必须首先得会 Dart。
Dart 环境搭建:
要在我们本地开发 Dart 程序的话首先需要安装 Dart Sdk,官方文档:https://dart.dev/get-dart
Dart 开发工具:
1、找到 vscode 插件安装 Dart;
2、找到 vscode 插件安装 Code Runner,右键直接运行我们的文件;
2 变量、长量、命名规则
Dart 变量:
dart 是一个强大的脚本类语言,可以不预先定义变量类型 ,自动会类型推倒
dart 中定义变量可以通过 var 关键字可以通过类型来申明变量
如:
var str='this is var';
String str='this is var';
int str=123;
注意: var 后就不要写类型 , 写了类型 不要 var 两者都写 var a int = 5; 报错
Dart 常量: final 和 const 修饰符
const 值不变 一开始就得赋值
final 可以开始不赋值 只能赋一次 ; 而 final 不仅有const 的编译时常量的特性,最重要的它是运行时常量,并且 final 是惰性初始化,即在运行时第一次使用前才初始化
永远不改量的量,请使用 final 或 const 修饰它,而不是使用 var 或其他变量类型。
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
// const 常量
// const PI = 3.14159;
// PI = 123.1243; // 错误的写法 常量不可以修改
// print(PI);
// final 常量
// final PI = 3.14159;
// PI = 124214.214124; //错误写法
// print(PI);
// final a = new DateTime.now();
// print(a); // 2020-06-25 14:52:06.987503
// const a = new DateTime.now(); // 报错了
// 区别:final 可以开始不赋值 只能赋一次 ; 而 final 不仅有 const 的编译时常量的特性,最重要的它是运行时常量,并且 final 是惰性初始化,即在运行时第一次使用前才初始化
}
Dart的命名规则:
1、变量名称必须由数字、字母、下划线和美元符($)组成。
2.注意:标识符开头不能是数字
3.标识符不能是保留字和关键字。
4.变量的名字是区分大小写的如: age 和 Age 是不同的变量。在实际的运用中,也建议,不要用一个单词大小写区分两个变量。
5、标识符(变量名称)一定要见名思意 :变量名称建议用名词,方法名称建议用动词
3 常用数据类型
/*
Dart中支持以下数据类型:
常用数据类型:
Numbers(数值):
int
double
Strings(字符串)
String
Booleans(布尔)
bool
List(数组)
在Dart中,数组是列表对象,所以大多数人只是称它们为列表
Maps(字典)
通常来说,Map 是一个键值对相关的对象。 键和值可以是任何类型的对象。每个 键 只出现一次, 而一个值则可以出现多次
项目中用不到的数据类型 (用不到):
Runes
Rune是UTF-32编码的字符串。它可以通过文字转换成符号表情或者代表特定的文字。
main() {
var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());
Runes input = new Runes(
'\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}');
print(new String.fromCharCodes(input));
}
Symbols
Symbol对象表示在Dart程序中声明的运算符或标识符。您可能永远不需要使用符号,但它们对于按名称引用标识符的API非常有用,因为缩小会更改标识符名称而不会更改标识符符号。要获取标识符的符号,请使用符号文字,它只是#后跟标识符:
在 Dart 中符号用 # 开头来表示,入门阶段不需要了解这东西,可能永远也用不上。
http://dart.goodev.org/guides/libraries/library-tour#dartmirrors---reflection
*/
4 字符串拼接
Dart 字符串拼接跟 es6 还是有区别
String str1 = '你好';
String str2 = 'Dart';
print("$str1 $str2"); // 你好 Dart
print("${str1} ${str2}"); // 你好 Dart
5 数据类型判断
is 关键词来判断类型
var str=123;
if(str is String){
print('是string类型');
}else if(str is int){
print('int');
}else{
print('其他类型');
}
6 循环语句,break 和 continue
for 循环语句
// 定义一个二维数组 打印里面的内容
List list = [
{
"cate": '国内',
"news": [
{"title": "国内新闻1"},
{"title": "国内新闻2"},
{"title": "国内新闻3"}
]
},
{
"cate": '国际',
"news": [
{"title": "国际新闻1"},
{"title": "国际新闻2"},
{"title": "国际新闻3"}
]
}
];
for (var i = 0; i < list.length; i++) {
print(list[i]["cate"]);
print('-------------');
for (var j = 0; j < list[i]["news"].length; j++) {
print(list[i]["news"][j]["title"]);
}
}
输出:
/*
国内
国内新闻1
国内新闻2
国内新闻3
国际
国际新闻1
国际新闻2
*/
break 和 continue
break 语句功能:
1、在 switch 语句中使流程跳出 switch 结构。
2、在循环语句中使流程跳出当前循环,遇到 break 循环终止,后面代码也不会执行
强调:
1、如果在循环中已经执行了 break 语句,就不会执行循环体中位于break后的语句。
2、在多层循环中,一个 break 语句只能向外跳出一层
break 可以用在 switch case 中 也可以用在 for 循环和 while 循环中
continue 语句的功能:
【注】只能在循环语句中使用,使本次循环结束,即跳过循环体重下面尚未执行的语句,接着进行下次的是否执行循环的判断;
continue可以用在 for 循环以及 while 循环中,但是不建议用在 while 循环中,不小心容易死循环;
main() {
// for(var i=1;i<=10;i++){
// print(i);
// }
//1、如果i等于4的话跳过
// for(var i=1;i<=10;i++){
// if(i==4){
// continue; /*跳过当前循环体 然后循环还会继续执行*/
// }
// print(i);
// }
//2、如果 i等于4的话跳出循环
// for(var i=1;i<=10;i++){
// if(i==4){
// break; /*跳出循环体*/
// }
// print(i);
// }
//3、break语句只能向外跳出一层
// for(var i=0;i<5;i++){
// print('外层---$i');
// for(var j=0;j<3;j++){
// if(j==1){
// break;
// }
// print('里层$j');
// }
// }
//4、while循环 break跳出循环
// var i = 1;
// while (i <= 10) {
// if (i == 4) {
// break;
// }
// print(i);
// i++;
// }
var sex = "男";
switch (sex) {
case "男":
print('男');
break;
case "女":
print('男');
break;
default:
}
}
7 List、Set、Map
- List
/*
List里面常用的属性和方法:
常用属性:
length 长度
reversed 翻转
isEmpty 是否为空
isNotEmpty 是否不为空
常用方法:
add 增加
addAll 拼接数组
indexOf 查找 传入具体值
remove 删除 传入具体值
removeAt 删除 传入索引值
fillRange 修改
insert(index,value); 指定位置插入
insertAll(index,list) 指定位置插入List
toList() 其他类型转换成List
join() List转换成字符串
split() 字符串转化成List
forEach
map
where
any
every
*/
void main() {
// List myList=['香蕉','苹果','西瓜'];
// print(myList[1]);
// var list=new List();
// list.add('111');
// list.add('222');
// print(list);
// List里面的属性:
// List myList=['香蕉','苹果','西瓜'];
// print(myList.length);
// print(myList.isEmpty);
// print(myList.isNotEmpty);
// print(myList.reversed); // 对列表倒序排序
// var newMyList=myList.reversed.toList();
// print(newMyList);
// List里面的方法:
// List myList=['香蕉','苹果','西瓜'];
// myList.add('桃子'); // 增加数据 增加一个
// myList.addAll(['桃子','葡萄']); // 拼接数组
// print(myList);
// print(myList.indexOf('苹x果')); // indexOf查找数据 查找不到返回-1 查找到返回索引值
// myList.remove('西瓜');
// myList.removeAt(1);
// print(myList);
List myList = ['香蕉', '苹果', '西瓜'];
// myList.fillRange(1, 2, 'aaa'); // 修改 [香蕉, aaa, 西瓜],下标从0开始,包括起始位置,不包括结束位置
myList.fillRange(1, 3, 'aaa'); // [香蕉, aaa, aaa]
// myList.insert(1,'aaa'); // 插入 一个
// myList.insertAll(1, ['aaa','bbb']); // 插入 多个
print(myList);
// List myList=['香蕉','苹果','西瓜'];
// var str=myList.join('-'); //list转换成字符串
// print(str);
// print(str is String); //true
// var str = '香蕉-苹果-西瓜';
// var list = str.split('-');
// print(list);
// print(list is List);
}
- Set
//Set
// 用它最主要的功能就是去除数组重复内容
// Set 是没有顺序且不能重复的集合,所以不能通过索引去获取值
void main() {
// var s=new Set();
// s.add('香蕉');
// s.add('苹果');
// s.add('苹果');
// print(s); // {香蕉, 苹果}
// print(s.toList()); // [香蕉, 苹果]
List myList = ['香蕉', '苹果', '西瓜', '香蕉', '苹果', '香蕉', '苹果'];
var s = new Set();
s.addAll(myList);
print(s); // {香蕉, 苹果, 西瓜}
print(s.toList()); // [香蕉, 苹果, 西瓜]
}
- Map
/*
映射(Maps)是无序的键值对:
常用属性:
keys 获取所有的key值
values 获取所有的value值
isEmpty 是否为空
isNotEmpty 是否不为空
常用方法:
remove(key) 删除指定key的数据
addAll({...}) 合并映射 给映射内增加属性
containsValue 查看映射内的值 返回true/false
forEach
map
where
any
every
*/
void main() {
// Map person = {"name": "张三", "age": 20};
// var m = new Map();
// m["name"] = "李四";
// print(person); // {name: 张三, age: 20}
// print(m); // {name: 李四}
//常用属性:
// Map person = {"name": "张三", "age": 20, "sex": "男"};
// print(person.keys.toList()); // [name, age, sex]
// print(person.values.toList()); // [张三, 20, 男]
// print(person.isEmpty); // false
// print(person.isNotEmpty); // true
//常用方法:
Map person = {"name": "张三", "age": 20, "sex": "男"};
person.addAll({
"work": ['敲代码', '送外卖'],
"height": 160
});
// print(person); // {name: 张三, age: 20, sex: 男, work: [敲代码, 送外卖], height: 160}
person.remove("sex");
print(person); // {name: 张三, age: 20, work: [敲代码, 送外卖], height: 160}
print(person.containsValue('张三')); // true
}
- forEach、map、where、any、every
void main() {
// List myList = ['香蕉', '苹果', '西瓜'];
// for (var i = 0; i < myList.length; i++) {
// print(myList[i]); // 香蕉 苹果 西瓜
// }
// for (var item in myList) {
// print(item); // 香蕉 苹果 西瓜
// }
// myList.forEach((value) {
// print("$value"); // 香蕉 苹果 西瓜
// });
// List myList = [1, 3, 4];
// List newList = new List();
// for (var i = 0; i < myList.length; i++) {
// newList.add(myList[i] * 2);
// }
// print(newList); // [2, 6, 8]
// List myList = [1, 3, 4];
// var newList = myList.map((value) {
// return value * 2;
// });
// print(newList.toList()); // [2, 6, 8]
// List myList = [1, 3, 4, 5, 7, 8, 9];
// var newList = myList.where((value) {
// return value > 5;
// });
// print(newList.toList()); // [7, 8, 9]
// List myList = [1, 3, 4, 5, 7, 8, 9];
// var f = myList.any((value) {
// //只要集合里面有满足条件的就返回 true
// return value > 5; // true
// });
// print(f);
// List myList = [1, 3, 4, 5, 7, 8, 9];
// var f = myList.every((value) {
// //每一个都满足条件返回true 否则返回 false
// return value > 5; // false
// });
// print(f);
// set
// var s = new Set();
// s.addAll([1, 222, 333]);
// s.forEach((value) => print(value)); // 1 222 333
//map
Map person = {"name": "张三", "age": 20};
person.forEach((key, value) {
print("$key---$value"); // name---张三 age---20
});
}
8 函数
8.1 内置函数和自定义函数,以及函数的作用域:
/*
内置方法/函数:
print();
自定义方法:
自定义方法的基本格式:
返回类型 方法名称(参数1,参数2,...){
方法体
return 返回值;
}
*/
void printInfo() {
print('我是一个自定义方法');
}
int getNum() {
var myNum = 123;
return myNum;
}
String printUserInfo() {
return 'this is str';
}
List getList() {
return ['111', '2222', '333'];
}
void main() {
// print('调用系统内置的方法');
// printInfo();
// var n=getNum();
// print(n);
// print(printUserInfo());
// print(getList());
// print(getList());
// 演示方法的作用域
void xxx() {
aaa() {
print(getList());
print('aaa');
}
aaa();
}
// aaa(); 错误写法
xxx(); // 调用方法
}
8.2 默认参数
- 方式一,中括号,参数顺序要对应
String printUserInfo(String username, [String sex = '男', int age]) {
// 行参
if (age != null) {
return "姓名:$username---性别:$sex--年龄:$age";
}
return "姓名:$username---性别:$sex--年龄保密";
}
print(printUserInfo('张三'));
print(printUserInfo('小李','女'));
print(printUserInfo('小李','女',30));
- 方式二,大括号,参数字段要对应,跟顺序无关
String printUserInfo(String username, {int age, String sex = '男'}) {
//行参
if (age != null) {
return "姓名:$username---性别:$sex--年龄:$age";
}
return "姓名:$username---性别:$sex--年龄保密";
}
print(printUserInfo('张三', age: 20, sex: '未知'));
8.3 箭头函数
箭头函数跟 es6 还是不太一样,只能是一条语句,建议不要使用
/*需求:使用forEach打印下面List里面的数据*/
List list = ['苹果', '香蕉', '西瓜'];
list.forEach((value){ // 建议使用这种普通用法
print(value);
});
// list.forEach((value)=>print(value));
// list.forEach((value) => {print(value)}); // 箭头函数只能是一行
/*需求:修改下面List里面的数据,让数组中大于2的值乘以2*/
List list = [4, 1, 2, 3, 4];
var newList=list.map((value){
if(value>2){
return value*2;
}
return value;
});
print(newList.toList());
8.4 闭包
闭包特点:
1、全局变量特点: 全局变量常驻内存、全局变量污染全局
2、局部变量的特点: 不常驻内存会被垃圾机制回收、不会污染全局
函数嵌套函数, 内部函数会调用外部函数的变量或参数, 变量或参数不会被系统回收(不会释放内存)
闭包的写法: 函数嵌套函数,并 return 里面的函数,这样就形成了闭包。
/*全局变量*/
// var a=123;
void main(){
// 闭包,函数里面返回函数
fn() {
var a = 123; /*不会污染全局 常驻内存*/
return () {
a++;
print(a);
};
}
var b = fn(); // 返回一个函数体
b(); // 调用返回的函数体
b();
b();
}
9 Dart 中的对象和类
面向对象编程(OOP)的三个基本特征是:封装、继承、多态
封装:封装是对象和类概念的主要特性。封装,把客观事物封装成抽象的类,并且把自己的部分属性和方法提供给其他对象调用, 而一部分属性和方法则隐藏。
继承:面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:允许将子类类型的指针赋值给父类类型的指针, 同一个函数调用会有不同的执行效果 。
Dart 所有的东西都是对象,所有的对象都继承自 Object 类。
Dart 是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是 Object 的子类
一个类通常由属性和方法组成。
void main() {
List list = new List();
list.isEmpty;
list.add('香蕉');
list.add('香蕉1');
Map m = new Map();
m["username"] = "张三";
m.addAll({"age": 20});
m.isEmpty;
Object a = 123;
Object v = true;
print(a);
print(v);
}
9.1 默认构造函数
// class Person{
// String name='张三';
// int age=20;
// //默认构造函数
// Person(){
// print('这是构造函数里面的内容 这个方法在实例化的时候触发');
// }
// void printInfo(){
// print("${this.name}----${this.age}");
// }
// }
//张三 李四 王五
// class Person{
// String name;
// int age;
// //默认构造函数
// Person(String name,int age){
// this.name=name;
// this.age=age;
// }
// void printInfo(){
// print("${this.name}----${this.age}");
// }
// }
class Person {
String name;
int age;
// 默认构造函数的简写!!!
Person(this.name, this.age);
void printInfo() {
print("${this.name}----${this.age}");
}
}
void main() {
Person p1 = new Person('张三', 20);
p1.printInfo();
Person p2 = new Person('李四', 25);
p2.printInfo();
}
9.2 自定义命名构造函数
dart 里面构造函数可以写多个
class Person {
String name;
int age;
// 默认构造函数的简写
Person(this.name, this.age);
Person.now() {
print('我是命名构造函数');
}
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print("${this.name}----${this.age}");
}
}
void main() {
// var d=new DateTime.now(); // 实例化DateTime调用它的命名构造函数
// print(d);
//Person p1=new Person('张三', 20); // 默认实例化类的时候调用的是 默认构造函数
//Person p1=new Person.now(); // 命名构造函数调用
Person p1 = new Person.setInfo('李四', 30);
p1.printInfo();
}
9.3 私有属性和私用方法
Dart 和其他面向对象语言不一样,Data 中没有 public private protected 这些访问修饰符合
但是我们可以使用_把一个属性或者方法定义成私有。
lib/Animal
class Animal {
String _name; // 私有属性
int age;
// 默认构造函数的简写
Animal(this._name, this.age);
void printInfo() {
print("${this._name}----${this.age}");
}
String getName() {
return this._name;
}
void _run() {
print('这是一个私有方法');
}
execRun() {
this._run(); //类里面方法的相互调用
}
}
import 'lib/Animal.dart';
void main(){
Animal a=new Animal('小狗', 3);
print(a.getName());
a.execRun(); // 间接的调用私有方法
}
9.4 getter 和 setter 修饰符
class Rect {
num height;
num width;
Rect(this.height, this.width);
get area { // 没有括号
return this.height * this.width;
}
set areaHeight(value) {
this.height = value;
}
}
void main() {
Rect r = new Rect(10, 4);
// print("面积:${r.area()}");
r.areaHeight = 6; // 调用 set
print(r.area); // 调用 get
}
初始化实例变量
Dart 中我们也可以在构造函数体运行之前初始化实例变量
class Rect {
int height;
int width;
Rect(): height = 2, width = 10 { // 运行构造函数之前初始化实例变量
print("${this.height}---${this.width}");
}
getArea(){
return this.height*this.width;
}
}
void main(){
Rect r=new Rect();
print(r.getArea());
}
10 Dart 中的类、静态成员、操作符、类的继承
10.1 静态成员
1、使用 static 关键字来实现类级别的变量和函数;
2、静态方法不能访问非静态成员,非静态方法可以访问静态成员;
3、类的每个实例都可以访问类中定义的静态属性,所以你可以利用静态属性来设置值,该值可以被类的所有对象使用共享;
4、不需要实例化对象就能访问静态属性或方法,这样我们就不用为了获取一个简单的功能而实例化对象了
// class Person {
// static String name = '张三';
// static void show() {
// print(name);
// }
// }
// main(){
// print(Person.name);
// Person.show();
// }
class Person {
static String name = '张三';
int age = 20;
static void show() {
print(name);
}
void printInfo() {
/*非静态方法可以访问静态成员以及非静态成员*/
// print(name); //访问静态属性
// print(this.age); //访问非静态属性
show(); //调用静态方法
}
static void printUserInfo() {
//静态方法
print(name); //静态属性
show(); //静态方法
// print(this.age); // 静态方法没法访问非静态的属性
// this.printInfo(); // 静态方法没法访问非静态的方法
// printInfo();
}
}
main() {
// print(Person.name);
// Person.show();
// Person p = new Person();
// p.printInfo(); // 每个对象都可以直接访问类都静态属性或者静态方法
Person.printUserInfo();
}
10.2 Dart 中的对象操作符:
- ? 条件运算符 (了解)
- as 类型转换
- is 类型判断
- .. 级联操作 (连缀) (记住)
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
}
main() {
// Person p;
// p?.printInfo(); // 条件运算符,如果 p 实例存在,则执行后面的方法
// Person p=new Person('张三', 20);
// p?.printInfo();
// -------------------------------------------------------------
// Person p=new Person('张三', 20);
// if(p is Person){ // 判断类型
// p.name="李四";
// }
// p.printInfo();
// print(p is Object);
// -------------------------------------------------------------
// var p1;
// p1='';
// p1=new Person('张三1', 20);
// // p1.printInfo();
// (p1 as Person).printInfo(); // 类型转换
// -------------------------------------------------------------
// Person p1=new Person('张三1', 20);
// p1.printInfo();
// p1.name='张三222';
// p1.age=40;
// p1.printInfo();
Person p1 = new Person('张三1', 20);
p1.printInfo();
p1
..name = "李四"
..age = 30
..printInfo(); // 级联操作
}
10.3 类的继承
面向对象的三大特性:封装 、继承、多态;
-
Dart中的类的继承:
1、子类使用 extends 关键词来继承父类
2、子类会继承父类里面可见的属性和方法 但是不会继承构造函数
3、子类能复写父类的方法 getter 和 setter
class Person {
String name = '张三';
num age = 20;
void printInfo() {
print("${this.name}---${this.age}");
}
}
class Web extends Person {}
main() {
Web w = new Web();
print(w.name);
w.printInfo();
}
10.3.1 super 关键字
super 关键字就是指的是父类
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
}
class Web extends Person {
// super 关键字就是指的是父类,即 Person,super(name, age) 也就是调用 Person 构造函数
Web(String name, num age) : super(name, age) {}
}
main() {
// Person p=new Person('李四',20);
// p.printInfo();
// Person p1=new Person('张三',20);
// p1.printInfo();
Web w = new Web('张三', 12);
w.printInfo();
}
如果子类构造函数参数和父类构造函数参数不一致时候,该传给父类的参数就传:
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;
}
run() {
print("${this.name}---${this.age}--${this.sex}");
}
}
main() {
// Person p=new Person('李四',20);
// p.printInfo();
// Person p1=new Person('张三',20);
// p1.printInfo();
Web w = new Web('张三', 12, "男");
w.printInfo();
w.run();
}
10.3.2 实例化自类给命名构造函数传参
class Person {
String name;
num age;
Person(this.name, this.age);
Person.xxx(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.xxx(name, age) {
this.sex = sex;
}
run() {
print("${this.name}---${this.age}--${this.sex}");
}
}
main() {
// Person p=new Person('李四',20);
// p.printInfo();
// Person p1=new Person('张三',20);
// p1.printInfo();
Web w = new Web('张三', 12, "男");
w.printInfo();
w.run();
}
10.3.3 覆写父类的方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
work() {
print("${this.name}在工作...");
}
}
class Web extends Person {
Web(String name, num age) : super(name, age);
run() {
print('run');
}
// 覆写父类的方法
@override // 可以写也可以不写 建议在覆写父类方法的时候加上 @override
void printInfo() {
print("姓名:${this.name}---年龄:${this.age}");
}
// 覆写父类的方法
@override
work() {
print("${this.name}的工作是写代码");
}
}
main() {
Web w = new Web('李四', 20);
w.printInfo();
w.work();
}
10.3.4 自类里面调用父类的方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
work() {
print("${this.name}在工作...");
}
}
class Web extends Person {
Web(String name, num age) : super(name, age);
run() {
print('run');
super.work(); // 自类调用父类的方法!!!
}
//覆写父类的方法
@override // 可以写也可以不写 建议在覆写父类方法的时候加上 @override
void printInfo() {
print("姓名:${this.name}---年龄:${this.age}");
}
}
main() {
Web w = new Web('李四', 20);
// w.printInfo();
w.run();
}
11 Dart 中的抽象类、多态、以及接口
11.1 抽象类
Dart 中抽象类: Dart 抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
1、抽象类通过 abstract
关键字来定义;
2、Dart 中的抽象方法不能用 abstract 声明,Dart 中没有方法体的方法我们称为抽象方法;
3、如果子类继承抽象类必须得实现里面的抽象方法;
4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法;
5、抽象类不能被实例化,只有继承它的子类可以。
extends 抽象类 和 implements的区别:
1、如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用 extends 继承抽象类
2、如果只是把抽象类当做标准的话我们就用 implements 实现抽象类
案例:定义一个Animal 类要求它的子类必须包含 eat 方法
abstract class Animal {
eat(); // 抽象方法
run(); // 抽象方法
printInfo() {
// 不是抽象方法,因为有实现该方法了
print('我是一个抽象类里面的普通方法');
}
}
class Dog extends Animal { // 继承抽象类,必须实现其抽象方法
@override
eat() {
print('小狗在吃骨头');
}
@override
run() {
// TODO: implement run
print('小狗在跑');
}
}
class Cat extends Animal { // 继承抽象类,必须实现其抽象方法
@override
eat() {
// TODO: implement eat
print('小猫在吃老鼠');
}
@override
run() {
// TODO: implement run
print('小猫在跑');
}
}
main() {
Dog d = new Dog();
d.eat();
d.printInfo();
Cat c = new Cat();
c.eat();
c.printInfo();
// Animal a=new Animal(); // 抽象类没法直接被实例化
}
11.2 多态
Datr 中的多态:允许将子类类型的指针赋值给父类类型的指针, 同一个函数调用会有不同的执行效果 。
简单的理解,多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类有不同的表现。
子类的实例赋值给父类的引用。
abstract class Animal {
eat(); //抽象方法
}
class Dog extends Animal {
@override
eat() {
print('小狗在吃骨头');
}
run() {
print('run');
}
}
class Cat extends Animal {
@override
eat() {
print('小猫在吃老鼠');
}
run() {
print('run');
}
}
main() {
// Dog d=new Dog();
// d.eat();
// d.run();
// Cat c=new Cat();
// c.eat();
Animal d = new Dog();
d.eat();
Animal c = new Cat();
c.eat();
}
11.3 接口
和 Java 一样,dart 也有接口,但是和 Java 还是有区别的。
首先,dart 的接口没有 interface
关键字定义接口,而是普通类或抽象类都可以作为接口被实现。
同样使用 implements
关键字进行实现。
但是 dart 的接口有点奇怪,如果实现的类是普通类,会将普通类和抽象中的属性的方法全部需要覆写一遍。
而因为抽象类可以定义抽象方法,普通类不可以,所以一般如果要实现像 Java 接口那样的方式,一般会使用抽象类。
建议使用抽象类定义接口。
/*
定义一个DB库 支持 mysql mssql mongodb
mysql mssql mongodb三个类里面都有同样的方法
*/
abstract class Db {
//当做接口 接口:就是约定 、规范
String uri; //数据库的链接地址
add(String data);
save();
delete();
}
class Mysql implements Db {
@override
String uri;
Mysql(this.uri);
@override
add(data) {
// TODO: implement add
print('这是mysql的add方法' + data);
}
@override
delete() {
// TODO: implement delete
return null;
}
@override
save() {
// TODO: implement save
return null;
}
remove() {}
}
class MsSql implements Db {
@override
String uri;
@override
add(String data) {
print('这是mssql的add方法' + data);
}
@override
delete() {
// TODO: implement delete
return null;
}
@override
save() {
// TODO: implement save
return null;
}
}
main() {
Mysql mysql = new Mysql('xxxxxx');
mysql.add('1243214');
}
优化文件抽离,在根目录新建 lib 目录:
abstract class Db {
//当做接口 接口:就是约定 、规范
String uri; //数据库的链接地址
add(String data);
save();
delete();
}
import 'Db.dart';
class MsSql implements Db {
@override
String uri;
@override
add(String data) {
print('这是mssql的add方法' + data);
}
@override
delete() {
// TODO: implement delete
return null;
}
@override
save() {
// TODO: implement save
return null;
}
}
import 'Db.dart';
class Mysql implements Db {
@override
String uri;
Mysql(this.uri);
@override
add(data) {
print('这是mysql的add方法' + data);
}
@override
delete() {
return null;
}
@override
save() {
return null;
}
}
/*
定义一个DB库 支持 mysql mssql mongodb
mysql mssql mongodb三个类里面都有同样的方法
*/
// import 'lib/Mysql.dart';
import 'lib/MsSql.dart';
main() {
// Mysql mysql=new Mysql('xxxxxx');
// mysql.add('1243214');
MsSql mssql = new MsSql();
mssql.uri = '127.0.0.1';
mssql.add('增加的数据');
}
12 Dart 中一个类实现多个接口,以及 Dart 中的 Mixins
12.1 Dart 中一个类实现多个接口
abstract class A {
String name;
printA();
}
abstract class B {
printB();
}
class C implements A, B {
@override
String name;
@override
printA() {
print('printA');
}
@override
printB() {
// TODO: implement printB
return null;
}
}
void main() {
C c = new C();
c.printA();
}
12.2 mixins
mixins 的中文意思是混入,就是在类中混入其他功能,在 Dart 中可以使用 mixins 实现类似多继承的功能;
随着Dart版本一直在变,这里例子使用的是 Dart2.x 中使用 mixins 的条件:
1、作为 mixins 的类只能继承自 Object,不能继承其他类
2、作为 mixins 的类不能有构造函数
3、一个类可以 mixins 多个 mixins 类
4、mixins 绝不是继承,也不是接口,而是一种全新的特性
- 使用 mixins 实现简单的类似继承功能
class A {
// 作为 mixins 的类只能继承自 Object,不能继承其他类
// 作为 mixins 的类不能有构造函数
String info = "this is A";
void printA() {
print("A");
}
}
class B {
void printB() {
print("B");
}
}
class C with A, B {
// 一个类可以 mixins 多个 mixins 类
// C mixins A 和 B,则实现类似多继承的功能,可以调用 A 和 B 的属性和方法
}
void main() {
var c = new C();
c.printA();
c.printB();
print(c.info);
// mixins 的实例类型是什么?很简单,mixins 的类型就是其超类的子类型
print(c is C); // true
print(c is A); // true
print(c is B); // true
}
- 使用 mixins,同时继承另一个类
class Person {
String name;
num age;
Person(this.name, this.age);
printInfo() {
print('${this.name}----${this.age}');
}
void run() {
print("Person Run");
}
}
class A {
String info = "this is A";
void printA() {
print("A");
}
void run() {
print("A Run");
}
}
class B {
void printB() {
print("B");
}
void run() {
print("B Run");
}
}
class C extends Person with B, A {
C(String name, num age) : super(name, age);
}
void main() {
var c = new C('张三', 20);
c.printInfo();
// c.printB();
// print(c.info);
c.run();
}
13 Dart 中的泛型
13.1 泛型方法
泛型就是解决类
、接口
方法的复用性、以及对不特定数据类型的支持(类型校验)
例如下面的例子只能返回 string 类型的数据
String getData(String value){
return value;
}
同时支持返回 string 类型和 int 类型 (代码冗余)
String getData1(String value){
return value;
}
int getData2(int value){
return value;
}
同时返回 string 类型和 number 类型,不指定类型可以解决这个问题
getData(value){
return value;
}
不指定类型放弃了类型检查。我们现在想实现的是传入什么返回什么。比如:传入 number 类型必须返回 number 类型,传入 string 类型必须返回 string 类型
T getData<T>(T value) {
return value;
}
getData<T>(T value){
return value;
}
应用:
void main(){
// getData<String>('你好');
print(getData<int>(12));
}
13.2 泛型类
集合 List 泛型类的用法
案例:把下面类转换成泛型类,要求 List 里面可以增加 int 类型的数据,也可以增加 String 类型的数据。但是每次调用增加的类型要统一
class PrintClass{
List list=new List<int>(); // 限定 List 中的元素只能为 int 类型
void add(int value){
this.list.add(value);
}
void printInfo(){
for(var i=0;i<this.list.length;i++){
print(this.list[i]);
}
}
}
PrintClass p=new PrintClass();
p.add(1);
p.add(12);
p.add(5);
p.printInfo();
改写为如下泛型
class PrintClass<T>{
List list=new List<T>();
void add(T value){
this.list.add(value);
}
void printInfo(){
for(var i=0;i<this.list.length;i++){
print(this.list[i]);
}
}
}
应用:
main() {
PrintClass p=new PrintClass<int>();
p.add(12);
p.add(23);
p.printInfo();
}
13.3 泛型接口
通过一个例子,例如要实现数据缓存的功能:有文件缓存、和内存缓存。内存缓存和文件缓存按照接口约束实现。
步骤为:
-
1、定义一个泛型接口,约束实现它的子类必须有 getByKey(key) 和 setByKey(key,value);
-
2、要求 setByKey 的时候的 value 的类型和实例化子类的时候指定的类型一致;
abstract class Cache<T>{ // 用抽象类来模拟接口,指定传入的泛型
getByKey(String key);
void setByKey(String key, T value); // 指定抽象方法,值的类型跟传入的泛型类型一致
}
class FlieCache<T> implements Cache<T>{ // 实现接口,同时也需要指定相同的泛型;其实也可以使用 extends 继承 Cache
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print("我是文件缓存 把key=${key} value=${value}的数据写入到了文件中");
}
}
class MemoryCache<T> implements Cache<T>{ // 实现接口,同时也需要指定相同的泛型;其实也可以使用 extends 继承 Cache
@override
getByKey(String key) {
return null;
}
@override
void setByKey(String key, T value) {
print("我是内存缓存 把key=${key} value=${value} -写入到了内存中");
}
}
void main(){
// MemoryCache m=new MemoryCache<String>();
// m.setByKey('index', '首页数据');
MemoryCache m=new MemoryCache<Map>();
m.setByKey('index', {"name":"张三","age":20});
}
14 Dart 中的库,自定义库、系统库、第三方库
14.1 Dart 中的库
在实际开发中,不可能所有的代码都在一个文件编写,需要把拆分为多个文件或者模块,这就需要使用到库的概念。
在 Dart 中,库的使用时通过 import
关键字引入的。
library
指令可以创建一个库,每个 Dart 文件都是一个库,即使没有使用library
指令来指定。
Dart 中的库主要有三种:
- 1、我们自定义的库
import 'lib/xxx.dart';
- 2、系统内置库
import 'dart:math';
import 'dart:io';
import 'dart:convert';
- 3、Pub 包管理系统中的库
可以从以下地址查询相关的库
https://pub.dev/packages
https://pub.flutter-io.cn/packages
https://pub.dartlang.org/flutter/
- 3.1、需要在自己想项目根目录新建一个 pubspec.yaml
- 3.2、在 pubspec.yaml 文件 然后配置名称 、描述、依赖等信息
- 3.3、然后运行 pub get 获取包下载到本地 (flutter 项目 使用
flutter pub get
下载) - 3.4、项目中引入库 import 'package:http/http.dart' as http; 看文档使用
例如:安装 http、date_format 两个库
name: xxx
description: 项目的描述.
dependencies:
http: ^0.12.0+2
date_format: ^1.0.6
14.2 Dart 中导入自己本地库
import 'lib/Animal.dart';
main(){
var a = new Animal('小黑狗', 20);
print(a.getName());
}
14.3 导入系统内置库
//import 'dart:io';
import "dart: math";
main(){
print(min(12,23));
print(max(12,25));
}
14.4 Dart http 示例
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
main() async {
var url = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";
// Await the http get response, then decode the json-formatted responce.
var response = await http.get(url);
if (response.statusCode == 200) {
var jsonResponse = convert.jsonDecode(response.body);
print(jsonResponse);
} else {
print("Request failed with status: ${response.statusCode}.");
}
}
14.5 Dart 库的重命名,Dart 冲突解决
当引入两个库中有相同名称标识符的时候,如果是 java 通常我们通过写上完整的包名路径来指定使用的具体标识符,甚至不用 import 都可以,但是 Dart 里面是必须 import 的。当冲突的时候,可以使用 as 关键字来指定库的前缀。如下例子所示:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
Element element1 = new Element(); // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.
例如:Person1、Person2 中的类都为 Person,则需要解决冲突
import 'lib/Person1.dart';
import 'lib/Person2.dart' as lib;
main(List<String> args) {
Person p1 = new Person('张三', 20);
p1.printInfo();
lib.Person p2 = new lib.Person('李四', 20);
p2.printInfo();
}
14.6 部分导入,使用 show,或者 hide
部分导入,如果只需要导入库的一部分,有两种模式:
- 模式一:只导入需要的部分,使用 show 关键字,如下例子所示:
import 'package:lib1/lib1.dart' show foo;
- 模式二:隐藏不需要的部分,使用 hide 关键字,如下例子所示:
import 'package:lib2/lib2.dart' hide foo;
示例:
myMath.dart
void getName(){
print('张三');
}
void getAge(){
print(20);
}
void getSex(){
print('男');
}
// import 'lib/myMath.dart' show getAge;
import 'lib/myMath.dart' hide getName;
void main(){
// getName();
getAge();
}
14.7 延迟加载
延迟加载也称为懒加载,可以在需要的时候再进行加载。
懒加载的最大好处是可以减少 APP 的启动时间。
懒加载使用 deferred as
关键字来指定,如下例子所示:
import 'package:deferred/hello.dart' deferred as hello;
当需要使用的时候,需要使用 loadLibrary()
方法来加载:
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
14.8 库的拆分,part 与 part of
有的时候一个库可能太大,不能方便的保存在一个文件当中。Dart 允许我们把一个库拆分成一个或者多个较小的 part 组件。或者我们想让某一些库共享它们的私有对象的时候,我们需要使用 part。
这里我们可以看到,parttest2.dart 是 part of 'parttest.dart' 的文件,可以理解为,parttest2 是 parttest 的一部分。在 parttest2.dart 中,我们并没有引入 package:flutter/material.dart 包就直接继承了 StatelessWidget,是因为,在 part 中,import 进来的库是共享命名空间的
。
不是所有的库都有名称,但如果使用 part 来构建库,那么库必须要命名。
library xxx;
每个子 part 都存放在各自的文件中。但是它们共享同一作用域,库的内部命名空间,以及所有的导入(import)。
下面这个例子是在 Json 反序列化时使用到库的拆分。
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'data.dart'; // 该文件组件作为 data.dart 的一部分
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Data _$DataFromJson(Map<String, dynamic> json) {
return Data(
by: json['by'] as String,
descendants: json['descendants'] as int,
id: json['id'] as int,
kids: (json['kids'] as List)?.map((e) => e as int)?.toList(),
score: json['score'] as int,
time: json['time'] as int,
title: json['title'] as String,
type: json['type'] as String,
url: json['url'] as String);
}
Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{
'by': instance.by,
'descendants': instance.descendants,
'id': instance.id,
'kids': instance.kids,
'score': instance.score,
'time': instance.time,
'title': instance.title,
'type': instance.type,
'url': instance.url
};
然后作为 data.dart 的一部分。
import 'package:json_annotation/json_annotation.dart';
part 'data.g.dart';
@JsonSerializable()
class Data{
final String by;
final int descendants;
final int id;
final List<int> kids;
final int score;
final int time;
final String title;
final String type;
final String url;
Data({this.by, this.descendants, this.id, this.kids, this.score, this.time,
this.title, this.type, this.url});
//反序列化
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
//序列化
Map<String, dynamic> toJson() => _$DataToJson(this);
}
在上述例子中,我们可以看到,part data.dart 中 Data 类中调用了part data.g.dart 的 DataFromJson(json) 方法,而第二个 part 中构建 DataFromJson(json) 方法返回 Data 对象并没有在 part data.g.dart 定义。它们彼此之间是共用的命名空间。并且私有对象也是可以互相访问的。
part 也可以通过 url 指明它所在的库,但是我们同样也是不推荐这样做的。
参考:https://juejin.im/post/5b601f40e51d4519575a5036
再举个简单的例子:
// 在 math.dart 文件中
library math;
part 'base.dart';
part 'random.dart';
// 在 base.dart 文件的开头
part of math;
// 在 random.dart 文件的开头
part of math;
// 实际使用单个方法时必须引入 math.dart
Part 与 import 有什么区别:
-
可见性:
如果说在 A 库中 import 了 B 库,A 库对 B 库是不可见的,也就是说 B 库是无法知道 A 库的存在的。而 part 的作用是将一个库拆分成较小的组件。两个或多个 part 共同构成了一个库,它们彼此之间是知道互相的存在的。 -
作用域:
import 不会完全共享作用域,而 part 之间是完全共享的。如果说在 A 库中 import 了 B 库,B 库 import 了 C 库,A 库是没有办法直接使用 C 库的对象的。而 B,C 若是 A 的 part,那么三者共享所有对象。并且包含所有导入。
14.9 导出 export
多个较小的库组合为一个较大的库或者重新导出库的一部分作为一个新的库;
export 各文件是互相不可见的,如果在 A 库中 export 了 B 库、C 库,A 库是包含 B、C 库的全部的;
//material.dart 这是一个组合库,建议不要在其中有功能代码
library material;
export 'src/material/about.dart';
export 'src/material/animated_icons.dart';
export 'src/material/app.dart';
export 'src/material/app_bar.dart';
参考:https://www.jianshu.com/p/01e08e9fda47
15 Dart 和 Flutter 代码格式化
15.1 dartfmt 命令(用于格式化 Dart 代码的命令)
全局安装 dartfmt:
$ pub global activate dart_style
$ dartfmt ...
使用 dartfmt
命令可以根据 Dart 指南 将你程序中多余的空格替换掉。这与你使用支持 Dart 的 IDE 或者编辑器格式化代码的效果相同。
dartfmt
命令接收一系列文件或目录作为参数。例如,下面是如何格式化当前目录下 bin
、lib
和 test
目录下的所有 Dart 文件示例:
$ dartfmt -w bin lib test
如果你不想覆盖文件,请去掉 -w
选项。源代码格式化后的结果将作为标准输出显示出来。
$ dartfmt bin/main.dart
arguments) {
{my_app.calculate()}!');
}
15.2 扩展 vscode 格式化 Dart 和 flutter 项目代码
需要安装相关的插件:
Dart & flutter 插件注意:如果 vscode 设置 "editor.defaultFormatter",则需要删除,vscode 会自动识别相关的文件进行格式化,例如:我的本地默认配置使用 prettier 格式化代码,则需要删除该配置,如下图:
15.3 使用flutter
命令自动格式化代码
您还可以使用以下flutter format
命令在命令行界面(CLI)中自动格式化代码:
$ flutter format <one or more paths>
-h, --help Print this usage information.
使用 ‘尾随逗号’
Flutter 代码通常涉及构建相当深的树状数据结构,例如在一个 build 方法中。 为了获得良好的自动格式化,我们建议您采用可选的尾部逗号。添加尾随逗号很简单:始终在函数、方法和构造函数的参数列表末尾添加尾随逗号,以便保留您的编码格式。 这将有助于自动格式化程序为 Flutter 样式代码插入适当的换行符。
这里是一个自动格式化公式格式化带有尾部逗号代码的示例:
Automatically formatted code with trailing commas如果没有尾部逗号,格式化后则会是下面这样:
Automatically formatted code without trailing commas15.4 参考
- dart_style 1.3.6
- 格式化 Flutter 代码
- https://dart.cn/tools/dartfmt
- vscode extension 'flutter' cannot format '.dart' #2300
16 枚举 enum
枚举用于定义命名常量值,使用 enum 关键字声明枚举类型。
语法:
enum enum_name {
enumeration list
}
-
enum_name 指定枚举类型名称;
-
enumeration list 是以逗号分隔的标识符列表;
枚举列表中的每个符号代表一个整数值,一个大于它之前的符号。默认情况下,第一个枚举符号的值为0。
enum Status {
none,
running,
stopped,
paused
}
示例:
enum Status {
none,
running,
stopped,
paused
}
void main() {
print(Status.values);
Status.values.forEach((v) => print('value: $v, index: ${v.index}'));
print('running: ${Status.running}, ${Status.running.index}');
print('running index: ${Status.values[1]}');
}
结果:
[Status.none, Status.running, Status.stopped, Status.paused]
value: Status.none, index: 0
value: Status.running, index: 1
value: Status.stopped, index: 2
value: Status.paused, index: 3
running: Status.running, 1
running index: Status.running