最近由于要启动mac跟Windows的项目开发,考虑到mac跟Windows写UI页面的便秘程度,决定调研一下同时支持多平台的flutter在当前项目是否可行,顺便记录一下学习的东西,以便后续翻阅;
-
var、final、const
声明可以有两种方式,一种是不指定类型,即使用var关键字;
/// 声明变量
// 不指定类型,即使用var关键字
var name = 'Bob';
// 当变量未赋初始值的时候,是个dynamic类型,可以随意赋值不同联系
var a;
a = 5;
a = 'ff';
// 当变量已经赋初始值的时候,再赋其他类型值就会报错,提示你要修改
var a = 666;
a = 'f' as int;
// 另一种是明确指定类型(Optional types)
String name = 'Bob';
// 在变量类型并不明确的情况下,可以使用dynamic关键字
dynamic name = 'Bob';
未初始化的变量默认值是 null。即使变量是数字类型默认值也是 null,因为在 Dart 中一切都是对象,数字类型也不例外。
使用过程中从来不会被修改的变量, 可以使用 final 或 const,而不是 var 或者其他类型,Final 变量的值只能被设置一次;
final name = 'Bob';
final name;
name = 'Bob';
const name = 'Bob';
const name; // error
name = 'Bob';
final跟const常量的区别在于,const修饰的常量,在声明的时候就得赋值;
num类型,取整除法运算符
算术运算符都是类似的大部分都是类似的,除了符号为~/ 做除法运算时对结果进行取整;
int a = 8;
print(a.isEven);// 偶数
print(a.isOdd); // 奇数
// double类型不能用isEven、isOdd
double b = 1;
b = 1.5;
int c = 2;
c = 1.5; // error
// double类型可以赋值整型,int类型不能赋值小数
// 取整运算符
var nums = [1,2,3,4,5];
nums.forEach((element) {
print(element~/2);
});
输出就是0 1 1 2 2
String
String str = 'chris';
print(str[0]);
print(str[1]);
print(str+'666');
print(str*2);
// 打印结果
flutter: c
flutter: h
flutter: chris666
flutter: chrischris
// format拼接,用$符号取值
int a = 10;
int b = 20;
int c = b - a;
print('a + b = ${a+b}');
print('a - b = $c');
// 转义字符,前面加个r即可
String str = 'chris\n666';
String str1 = r'chris\n666';
print(str);
print(str1);
// '''可以表示多行
String str = '''xxx
yyy
ddd
''';
print(str);
// 打印结果
flutter: xxx
yyy
ddd
list、map
list
可以存放不同类型的数据
var list1 = [1, 2, 'chris'];
list1.add("666");
list1.insert(1, 'value');
list1.remove('chris');
list1.sort(); // 排序
list1.sublist(1,2);
var list2 = [1, 2, 3];
var list3 = const [1, 2, 'chris']; // 不可变数组
list2.add(4);
list2.add("value"); // error list1初始化都是int类型,所以只能添加int类型
list3.add('666');// error
map
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
print(gifts.length);
print(gifts.keys);
print(gifts.values);
以上 Map 对象也可以使用 Map 构造函数创建:
var gifts = Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
// 创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const。
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
// constantMap[2] = 'Helium'; // error
// list转map
var list = ['one','two','three'];
list.asMap;
赋值运算符??=
当被赋值的对象为null时候才进行赋值,否则保持原值不变,例如:
var b = 10;
b ??= 100;
print(b);
条件运算符??
Dart有以下两个运算符,有时可用来替换 if-else 表达式, 让表达式更简洁:
***condition***
**?**
***expr1***
**:**
***expr2***
如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值。
***expr1***
**??**
***expr2***
如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值(类似kotlin的?:)。
如果赋值是根据布尔值, 考虑使用 ?:。
var visibility = isPublic ? 'public' : 'private';
如果赋值是基于判定是否为 null, 考虑使用 ??。
String playerName(String name) => name ?? 'Guest';
Dart方法中的箭头函数
Dart中的方法也是一个对象
返回值和参数可以省略,
当方法的执行语句只有一句的时候,可以使用箭头函数 => 表示;
String playerName(String name) {
return name ?? 'Guest';
}
// 等价于
String playerName(String name) => name ?? 'Guest';
方法中的可选参数
// 可选参数,传递参数必须带上形参的名字 ,不然程序不知道你要给哪一个参数赋值;
这跟扩展参数不一样,扩展参数是逗号后面可以一直写;
使用[]包裹的就是按照顺序来的可选型,不用传参数名
void function() {
print(sum1(1,c:3,b:4));
}
sum1(int a,{b, c}) {
b ??= 0;
c ??= 0;
return a + b + c;
}
void function() {
print(sum1(1,c:3,b:4));
}
void function2() {
print(sum1(1,2));
}
// 按照顺序来传参的可选型
sum2(int a,[int b = 0,int c = 0]) {
return a + b + c;
}
上面是没有指定类型的可选参数,如果不设置默认参数或者可空,就会报错
// nullability
sum1(int a,{int? b, int c = 0}) {
b ??= 0;
c ??= 0;
return a + b + c;
}
方法作为参数传递
作为参数保存或者传递给其他方法调用
void main() {
var hello = printHello;
hello();
}
void printHello() => print('Hello');
void main() {
var list = [1,2,3,4];
list.forEach(print);
}
匿名函数
void main() {
var func = () {
print('匿名方法');
};
func();
((){
print('立即执行方法');
})();
}
匿名方法也可以作为参数进行传递,这就有点像我们OC、Swift当中的闭包了,但是匿名方法跟闭包还是有一丢丢的区别的;
void main() {
var list = [1,2,3,4];
int b = 0;
forEachDemo(list, (var a) {
b++;
print('元素$b=$a');
});
};
void forEachDemo(List list, void func(var element)) {
for(var e in list) func(e);
}
闭包
首先闭包的概念是:定义在函数里面的函数就是闭包,闭包也是一个对象;
闭包的作用:可以访问外部函数的局部变量;
在iOS中,使用闭包最怕的就是闭包跟外部函数互相持有,造成循环引用;
下面一个例子,这段代码打印结果是0,1,2,3;
void main() => closureDemo();
void closureDemo() {
var func = funA(); // 此时func就是一个闭包
func();
func();
func();
func();
}
funA() {
int count = 0;
// 返回一个匿名函数给外部使用,这个匿名函数其实就是一个闭包
return () => print(count++);
}
这个就是因为闭包形成一个闭环,在栈上持续存在,没有被释放,所以它可以不断的访问外部函数的局部变量;如果我们在print里面传进去的是一个常量,此时也就不会存在闭环这一说;
类
Dart中带下划线的代表私有的东西,比如_age就是私有属性,_run代表私有方法,只有类内部才能访问,外部不能直接访问;
Dart中构造函数前面的的 new 关键字是可选的,可写也可不写;
void main() {
Person p = Person();
// p._age = 18; // error
p.height = 180;
// p._run(); // error
p.printP();
}
class Person {
final String name = "Chris";
int? _age;
int? height;
void _run() {
print("name:$name age:$age");
}
void printP() {
_run();
}
}
构造函数
void main() {
Person p1 = Person("Chris",18);
Person p2 = Person.withName("Chris",180);
p1.changeName(20);
p1.printP();
p2.printP();
}
class Person {
String? _name;
int? age;
int? height;
Person(String name, int age) {
this._name = name; // 也可写成_name = name;
this.age = age; // 如果写成age = age,就会赋值不进去
}
// 上面构造方法跟下面这个等价
Person(this._name, this.age);
// 命名构造函数
Person.withName(this._name, this.height);
changeName(int age) {
this.age = age;
}
_run() {
print("name:$_name,age:$age,height:$height");
}
printP() {
_run();
}
}
当一个对象所有的成员属性都被final修饰的时候,那么这个对象就可以被创建为常量 const,提高运行速度;
class Person() {
final String? _name;
final int? age;
const Person(this._name);
}
工厂构造、单例对象、初始化列表
再实际开发中经常会用到单例设计模式,比如我定义一个工厂类,需要它是一个单例,可以这么写;
class FactoryClass() {
// 单例,用全局static修饰符修饰
static FactoryClass? _instance;
factory FactoryClass() {
if(_instance == null) {
// 此处由于不能调用自身构造函数,所以可以写一个私有的_init命名构造函数;
_instance = FactoryClass._init();
}
// 构造函数不能带return,所以如果要加return,需要factory修饰构造方法
return _instance!; // return不能返回空,所以用!强制解包
}
// 上面方法可以简化为下面的写法,利用箭头函数,这就是一个最简单的单例方法了
factory FactoryClass() => _instance ??= FactoryClass._init();
// 私有的命名构造函数,可以创建一个新的对象
FactoryClass._init();
}
初始化列表
在开发期间, 可以使用 assert 来验证输入的初始化列表的数据正确性。
class Person {
String name;
int age;
final height;
Person(this.name,this.age,int h) : height = h,
assert(h >= 0),
assert(age >= 0) {
print("name:$name, age:$age, height:$height");
}
}
类方法和对象操作符
void staticDemo() {
// 调用静态属性
StaticClass.count = 10;
// 调用静态方法
print(StaticClass.sum(20));
}
class StaticClass() {
// 静态属性
static int count = 1;
// 实例属性
int current = 2;
// 静态方法
static int sum(int a) {
return a + count;
}
// 实例方法即可以访问对象属性,也可以访问静态属性
int sum2(int a) {
return a + count + current;
}
}
对象操作符
as、is、.. 操作符
void staticDemo() {
var s1 = Object();
s1 = StaticClass();
// as 强转
(s1 as StaticClass).sum2();
// is 判断
if(s1 is StaticClass) {
s1.sum2();
// ..操作符,链式编程,每次都是返回对象本身,所以此处打印的是对象
print(s1..current = 15..sum2(20));
}
}
class StaticClass() {
int current;
int sum2(int a) {
return a + current;
}
}
继承
Dart中使用extends继承一个类,它是单继承的形式;
子类会继承除了构造方法以外的属性方法;
void extendsDemo() {
Student st = Student();
st.run();
st.study;
st.name = "Chris";
st.age = 18;
print(st.isFree);
}
// 我们所写的所有的类,不写的情况下默认都是继承Object,所以class Person等价于下面
class Person extends Object {
String? name;
int? age;
int? _height;
bool get isFree => height! <= 110;
run() {
print("Person run...");
}
}
class Student extends Person {
study() {
print("good good study");
}
// override复写父类的东西
@override
bool isFree => age! < 18;
@override
run() {
print("Student run...");
}
}
有一个小细节需要注意的就是,在父类中有显性的实现构造方法,那么子类中至少得实现其中一个才行;
class Person {
String? name;
int? age;
int? _height;
Person(this.age);
Person.init();
Person.withName(this.name);
}
class Student extends Person {
final String subName;
// Student(String? name) : super(name);
// Student.init() : super.init();
Student.withName(String? name) : subName = name!, super.withName(name);
// 当然我们也可以自己写构造方法,只要实现其中一个就可以
// 这个super实现,跟初始化列表一样,我们也可以在前面写我们要初始化的东西,当然他们只能写在 super之前
Student() : subName = "Chris", super.init();
}
抽象类和接口
抽象类其实就有点类似于iOS中的协议,即只有声明方法,没有实现方法;
抽象类是不能被实例化的类,用abstract修饰
AbstractDemo() {
AbstractClass as = Student();
print(as.sum);
}
abstract class AbstractClass {
// 这就是抽象方法
int sum(int a, int b);
}
// 继承抽象类就必须实现抽象方法
class Student extends AbstractClass {
@override
int sum(int a, int b) {
return a + b;
}
}
在iOS中可以遵循多个协议,那么在Dart中是否也可以呢?
我们可以用implements来实现多个抽象类的实现,需要重写抽象类里的所有方法
AbstractDemo() {
AbstractClass as = Student();
print(as.sum);
}
abstract class AbstractClass1 {
int sum1(int a, int b);
}
abstract class AbstractClass2 {
int sum2(int a, int b);
}
abstract class AbstractClass3 {
int sum3(int a, int b);
}
class Student implements AbstractClass1, AbstractClass2, AbstractClass3 {
@override
int sum1(int a, int b) {
return a + b;
}
@override
int sum2(int a, int b) {
return a + b;
}
@override
int sum3(int a, int b) {
return a + b;
}
}
Mixins混入
混入,说白了就是一个多继承的形式
mixinDemo() {
D d = D();
d.a(); // 打印c,以最后一个加载的为主
}
class A {
a() => print("a.....");
}
class B {
a() => print("b.....");
b() => print("b.....");
}
class TestClass {}
// 参与混入的类,也不能继承于非Object类,就怕继承的类有构造方法
class C extends TestClass {
// C(); // 参与混入的类,不能有构造方法
a() => print("c.....");
c() => print("c.....");
}
// class D extends A with B, C {}
class D = A with B, C;
operator操作符
我们可以对操作符,大于、小于、等于等操作符进行重载,使用operator修饰符内部重新实现操作符的比较;
operatorDemo() {
OperatorClass opt1 = OperatorClass(10);
OperatorClass opt2 = OperatorClass(20);
print(opt1 > opt2);
}
class OperatorClass {
int age;
OperatorClass(this.age);
bool operator >(OperatorClass other) => this.age > other.age;
}
异步支持
方式1:使用async+await关键字
// 定义一个异步函数
Future requestSomething() async {
String result = await getSomething(); // 线程在此阻塞,等待getSomething执行完成
return result; // 不需要使用FutureApi,Dart会自动创建Future对象
}
String getSomething() {
return "my name is Dart";
}
方式2:使用Future提供的api
Future someOperations() {
return Future.value("my name is Dart");
}
// 操作完成后执行then回调
someOperations().then((value) => {
print(value)
})
// 操作执行过程中出错执行onError回调
.onError((error, stackTrace) => {
print("$error, $stackTrace")
});
- 处理Stream(异步事件序列):
方式1:使用async+await for关键字
例如需要连续执行100次sendAudioStream耗时方法,但是又要求这些任务按顺序依次执行
// 需要按顺序执行多次的耗时任务
Stream sendAudioStream(ByteData audioStream) {
print(audioStream.lengthInBytes);
return Stream.value(true);
}
// 对耗时任务结果的处理
Future handleStream(Stream sendAudioStream) async {
await for(var stream in sendAudioStream) {
print("sendAudioStream result is $stream");
await Future.delayed(Duration(milliseconds: 180));
}
return Future.value();
}
Iterable fromOneTo(int n) sync* {
int k = 1;
while(k < n) {
yield k;
k++;
}
}
void main() {
for (var i in fromOneTo(100)) {
// 将耗时任务加入队列中
handleStream(sendAudioStream(ByteData(1024)));
}
}
方式2:使用Stream提供的api
将上例中的handleStream方法改动为下方所示即可:
Future handleStream(Stream sendAudioStream) async {
sendAudioStream.listen((event) {
print("result is $event");
await Future.delayed(Duration(milliseconds: 180));
},
onError: (){
},
onDone: (){
});
return Future.value();
}