1.为什么要学习Flutter?
对于移动端开发人员来说,跨平台技术一直是关注的重点,从H5,React Native到Flutter,我们似乎一直在寻找一种能“一套代码,多端运行”,同时还能有不俗的用户体验的技术。对于当前的大前端来说,React Native的综合成熟度和生态都要比Flutter好一些,对于中短期项目我们可能会选用前者,但是对于更长期的项目和发展来说,Flutter是一种更彻底的解决方案,渲染能力和平台一致性以及性能,它都具有更大的优势。
我们从Web容器时代,比如H5,Cordova,微信小程序,Ionic,这种基于web相关技术通过浏览器组件来实现界面及其功能,在android上是WebView,IOS上为UIWebView,这种采用原生内嵌的浏览器来进行渲染,虽然可以做到多端一致,对于用户体验很是有限,尤其是一些复杂的UI逻辑。
-
对于泛Web容器时代来说,代表的技术有React Native和Weex。基本上是完全放弃了浏览器控件的渲染,而是采用原生自带UI组件实现代替了核心的渲染引擎,仅仅保持必要的基本控件渲染能力,简化渲染,保证了良好的渲染性能。这个时代总体来说是做一个平衡,依然采用了前端友好的Js进行开发,原生来接管绘制,依托于Js虚拟机的Js代码提供所需的UI控件的实体。
但是对于这种映射来说,在维护各个平台的API升级方面往往需要巨大的成本,对于Android/Ios平台上使用React Navie,对开发人员的要求不仅仅是懂React那么简单,而是还需要懂两个平台的一些基础开发知识。
-
接下来是自绘引擎时代,即从头到尾写一套跨平台的UI框架,包括渲染逻辑甚至开发语言。所以对于移动端的开发人员来说,其实还是有一定的学习成本的(但对于已掌握一门编程语言的人几乎可以忽略不计),但从平台一致性,维护成本,性能以及开发效率上都是全方位碾压之前两个时代的技术。
1.Dart语言同时支持JIT和AOT。开发周期使用JIT,大大缩短开发周期,调式模式支持有状态的热重载;而发布期使用AOT,本地代码的执行更高效,代码性能和用户体验也更优秀。Dart避免了抢占式调度和共享内存,可以在没有锁的情况下进行对象分配和垃圾回收,在性能方面表现很是卓越。
2.渲染引擎依靠跨平台的Skia图像绘制引擎,Skia将使用Dart构建的抽象的视图结构结构数据加工成GPU数据,然后数据通过OpenGL最终提供给GPU渲染,因为安卓上已经有Skia引擎了,这也是Flutter Android SDK 要比 Flutter iOS SDK 小一些的原因,打包后的apk也会比ipa小。
明白了为什么要学习Flutter,我们就更能坚定我们的脚步,要学好Flutter,就要先学习它的开发语言Dart,下面我们来看看Dart基础语法:
2.Dart基础语法
数据类型及其相关
含义 | 使用 | |
---|---|---|
int |
整数,范围为 -2^63 到 2^63 - 1. | int x = 1;//没有小数点就是int |
double |
浮点数,64位(双精度)浮点数 | double y = 1.1;//有小数点就是浮点数 |
num |
num 是数字类型,既可以表示整数,也可以表示浮点数,具体看赋的值 | num x = 1;//num x是整数 num y = 1.1;//num y是浮点数 |
String |
字符串 Dart字符串采用UTF-16编码 可以使用单引号或双引号来创建字符串 | var s1 = 'string'; var s2 = "string";字符串拼接采用“+” |
bool |
布尔值 | var isTrue = true; |
List |
List |
List |
Set |
Set |
Set |
Map |
Map |
Map |
Runes |
表示采用 UTF-32 的字符串,用于显示 Unicode 因为Dart字符串是UTF-16,因此在Dart中表示32位的Unicode值需要Runes这个特殊语法。 | Runes input = new Runes('\u{1f600}'); print(new String.fromCharCodes(input)); 打印出来是笑脸emoji: |
在Dart中,所有类型都是对象类型,都继承自顶层类型Object,因此一切变量都是对象,数字,布尔值,函数和null都不能例外。
未手动赋值的变量的值都是null,比如定义一个全局变量 int a,不进行手动赋值,打印出来a的值为null。
默认定义的变量都是public的,加个“_”则为private,限制范围并不是类访问级别的,而是库访问级别。
-
我们可以用var来声明变量而不需要指定特别的数据类型,但是一旦赋值后就不能改变数据类型的,而使用dynamic关键字就可以。
1.为什么var能定义所有变量?因为var 并不是直接存储值,而是存储的值的对象的引用,例如:var content = 'Dart 语法' 这句,是名字为 content 的 var 变量存储了值为 'Dart 语法' 的 String 对象的引用,所以 var 才能定义任何变量。
2.什么情况下用dynamic呢?当这个变量没法用 Dart 的类型来表示时,比如 Native 和 Flutter 交互,从 Native 传来的数据。所以你会看到 PlatformChannel 里有很多地方使用到了 dynamic。
final和Java中的final差不多,使用final和const的时候可以把var省略,使用const的时候如果是类里的变量就必须加static,全局变量时不需要的。在使用构造器的时候也可以使用const变量,但是这个时候就不需要方法体了。const是编译时常量,在编译时就进行初始化了,但是final变量是当类创建的时候才初始化。
函数
-
Dart不支持方法重载,而是提供了可选命名参数和可选参数:
给参数增加{},以 paramName: value 的方式指定调用参数,也就是可选命名参数;
给参数增加[],则意味着这些参数是可以忽略的,也就是可选参数。
//要达到可选命名参数的用法,那就在定义函数的时候给参数加上 {}
void test1Flags({bool bold, bool hidden}) => printTest("$bold , $hidden");
//定义可选命名参数时增加默认值
void test2Flags({bool bold = true, bool hidden = false}) => printTest("$bold ,$hidden");
//可忽略的参数在函数定义时用[]符号指定
void test3Flags(bool bold, [bool hidden]) => printTest("$bold ,$hidden");
//定义可忽略参数时增加默认值
void test4Flags(bool bold, [bool hidden = false]) => printTest("$bold ,$hidden");
//可选命名参数函数调用
test1Flags(bold: true, hidden: false); //true, false
test1Flags(bold: true); //true, null
test2Flags(bold: false); //false, false
//可忽略参数函数调用
test3Flags(true, false); //true, false
test3Flags(true,); //true, null
test4Flags(true); //true, false
test4Flags(true,true); // true, true
但是不可以同时把这符号同时放到同一个函数声明下面,比如:
void testFun({int a},[int b]){} //错误的写法
- 对于只有一行的函数可以用 =>来进行简写
void main() => runApp(MyApp());
等价于
void main(){
return runApp(MyApp());//runApp() 返回的是 void
}
- Dart还提供了命名构造函数的方式,使得类的实例化过程语义更加清晰。如下面实例,Point类中有两个构造函数,TestA.bottom和TestA
class TestA {
num x, y, z;
TestA(this.x, this.y) : z = 0; // 初始化变量z
TestA.bottom(num x) : this(x, 0); // 重定向构造函数
TestA.top():x=3; //重定向构造器
void printInfo() => print('($x,$y,$z)');
}
var p = TestA.bottom(100);
p.printInfo(); // 输出(100,0,0)
var p = TestA.top();
p.printInfo(); // 输出(3,0,0)
对于这种写构造器的方式,和我们用Java自定义控件构造器嵌套有着异曲同工之妙。
复用(extends,implement,mixin,on)
- 在Dart中,你可以对同一个父类进行继承或接口实现,但不可同时继承和实现。
class TestA{
num x,y,z;
TestA(this.x,this.y):z =0;
TestA.bottom(num x):this(x,0);
TestA.top():x=3;
void testCall() => print("($x=======,$y======)");
}
class TestB extends TestA{
TestB(num x, num y) : super(x, y);
@override
void testCall() { //重写testCall的实现
// TODO: implement testCall
super.testCall();
}
}
class TestC implements TestA{
@override
num x;
@override
num y;
@override
num z;
@override
void testCall() { //必须实现该方法
// TODO: implement testCall
}
}
可以看到,对于接口的实现方式,我们只是拿到了TestA的一个空壳子,不能复用原有的实现,这个时候,我们可以用混入(Mixin),混入鼓励代码重用,可以视为具有实现方法的接口,不仅可以解决Dart缺少多重继承的问题,同时也能避免多重继承可能导致的歧义(菱形问题)。要使用混入,我们需要使用with关键字,但是对于混入也有一定的要求,被混入的类不能有自己的构造器,否则就会提示错误。
class D{
num x,y;
D(this.x,this.y);
void testCall() => print("($x=======,$y======)");
}
class E with D{ //编译失败,提示D类不能被混入,因为已经定义了构造器。
}
至于为什么不能能用构造器,我觉得Mixin目前的实现是以完全忽略构造函数,忽略构造函数调用链的方式实现的,因此只支持隐式的构造函数。一旦放开mixin的构造函数支持,Dart势必需要支持一套新的关键字语法,来支持Mixin类继承链上的构造转发,代价太大。
- mixin还可以和on进行配合使用**,如果一个类mixin某个类,就必须先实现on的那个类,详情可以看看下面的代码:
class F{
}
mixin G on F{
}
class H extends F with G{ //必须先继承F类才可以,如果F是接口,那就必须先实现接口F
}
运算符
这里列出一些常用的表达,过于简单的就不列出来了:
操作符 | 含义 | 例子 |
---|---|---|
as | 类型转换 | (emp as Person).firstName = 'Bob'; 如果emp为null,抛出异常 |
is | 判断是否是某个类型,如果是的话,就返回 true | if (emp is Person) { // 如果 emp 是 Person 类型 emp.firstName ='Bob'; } 如果emp 为null ,返回false |
is! | 判断是否不是某个类型,如果不是的话,就返回 true | if (emp is! Person) { // 如果 emp 不是 Person 类型 } |
?. | 类似于kotlin中的空安全,为空跳过下面的逻辑,避免抛出异常 | |
??= | 用默认值兜底的一种方式 | a??=value,如果a为null,则给a赋值value,否则跳过 |
?? | 类似于三元表达式 | a ?? b 类似于(a != null)? a : b |
.. | 级联操作符,允许你对同一对象进行一系列的操作。 | querySelector('#confirm') // Get an object. ..text = 'Confirm' // Use its members. ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!')) |
- 运算符重载
我们可以通过运算符重载来实现更复杂的功能
// 自定义相加运算符,实现向量相加
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
// 覆写相等运算符,判断向量相等
bool operator == (dynamic v) => x == v.x && y == v.y;
3.写在最后
最近在研究Flutter,写这篇文章也是做一个简单的终结,希望更多的人加入Flutter的开发当中,最近开了一个淘宝店铺,店铺名字叫“程哥的日用百货铺”,这里附上链接 https://shop105359436.taobao.com/?spm=a1z10.1-c-s.0.0.c1b42dd2nh1YwP ,希望大家多多支持!
掘金链接:https://juejin.im/post/5e6ca09d6fb9a07c89151eda