第1章 Flutter的特点和核心概念
1.1 一切皆为组件
组件(Widget)是Flutter应用程序用哦过户界面的基本构建块。按钮,输入框,卡片,列表可以作为组件。Android中的View的布局方式,动画处理都是组件。所以Flutter可以被认为,Widget就如同Java中的Object。
1.2 组件嵌套
复杂的功能页面有一个个简单功能的组件组装完成。有的组件负责布局,有的负责定位,有的负责大小,有的负责渐变处理,等等。这种嵌套组合方式,带来最大的好处就是解耦。下面是flutter的hello world.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello World'),
),
),
);
}
}
无状态组件就是界面不会发生变化的组件,或者说,不需要和用户进行交互操作的组件,所谓交互就是点击,长按,滑动。有状态组件是界面会发生变化的组件,如滚动组件。
1.3.构建组件
可以重写widget的build方法来构建一个组件,如上面的HelloWorld程序。build返回的是一个Widget对象,不管是单个组件还是嵌套组合的组件,都是Widget实例。
1.4.处理用户交互
对于StatefulWidget,他们的可变状态存储在State子类中,当改变State对象时,必须调用setState()来通知框架,框架会再次调用State的构建方法来更新界面。
//主页需要继承自StatefulWidget
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
//标题
final String title;
//必须重写createState方法
@override
_MyHomePageState createState() => _MyHomePageState();
}
//状态类必须继承State类,注意后面需要指定为<MyHomePage>
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;//计数器
void _incrementCounter() {
//调用State类里的setState方法来更改状态值,使得计数器加1
setState(() {
//计数器变量,每次点击让其加1
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
//居中布局
body: Center(
//垂直布局
child: Column(
//主轴居中对齐
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'你点击右下角按钮的次数:',
),
Text(
'$_counter',//绑定计数器的值
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,//按下+号按钮调用自增函数
tooltip: '增加',
child: Icon(Icons.add),
),
);
}
}
1.5.什么是State
界面是由组件搭建的,组件中最重要的概念是State,State是一个组件的UI数据模型,是组件渲染时候的数据依据,同时内部也有对应的View的定义,如上面例子中的code。一些用户操作,比如点击,长按,滑动,推动State中的UI数据改变,然后通过调用setState()方法,实现View层的刷新。也就是build方法拿最新的数据重新渲染。flutter的状态的生命周期如下:
第2章 Flutter基础知识
2.1 入口程序
每一个flutter项目的/lib目录下都有一个main.dart文件,这里面有一个main(),这就是Flutter运行程序的入口,代码如下所示:
void main() => runApp(MyApp());
2.2 Flutter主题
主题分为全局主题和局部主题,全局主题就是由应用程序根MaterialApp创建的主题。
2.2.1 创建应用主题
创建主题的方法是讲ThemeData对象提供给MaterialApp构造函数,这样就可以在整个应用程序中共享包含颜色和字体样式的主题。ThemeData的主要属性通过查看类的构造函数的依赖参数可以了解。索引如下theme的部分:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "",
theme: ThemeData(brightness:Brightness.dark),
home: Scaffold(
appBar: AppBar(
title: const Text('Oh,Plugin example app'),
),
主题常用的有40个参数可以配置
2.2.2 创建局部主题
如果我们想要在应用程序的某一部分使用特殊的颜色,那么就要覆盖全局的主题。有两种方法:创建特有的主题数据或者扩展父主题。
- 创建特有的主题数据 直接实例化一个ThemeData并传递给Theme对象,代码如下:
theme: ThemeData(
//应用程序整体主题的亮度
brightness: Brightness.light,
//App主要部分的背景色
primaryColor: Colors.lightGreen[600],
//前景色(文本、按钮等)
accentColor: Colors.orange[600],
),
- 扩展父主题 扩展父主题时候无需覆盖所有的主题属性,我们通过使用copyWith来实现,代码如下:
floatingActionButton: Theme(
//使用copyWith的方式获取accentColor
data: Theme.of(context).copyWith(accentColor: Colors.grey),
child: FloatingActionButton(
onPressed: null,
child: Icon(Icons.computer),
),
),
2.2.3 使用主题
主题定义好之后就可以使用它了。函数Tmeme.of(context)可以通过上下文来获取主题,方式是查找最近的主题,如果找不到就会找整个应用的主题。
下面是一个小sample,应用的主题颜色定义为绿色,没解密那中间再加一个带有背景色的文本。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appName = '自定义主题';
return MaterialApp(
title: appName,
//主题配置
theme: ThemeData(
//应用程序整体主题的亮度
brightness: Brightness.light,
//App主要部分的背景色
primaryColor: Colors.lightGreen[600],
//前景色(文本、按钮等)
accentColor: Colors.orange[600],
),
home: MyHomePage(
title: appName,
),
);
}
}
class MyHomePage extends StatelessWidget {
//标题
final String title;
//接收title值 key为widget的唯一标识
MyHomePage({Key key, @required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Container(
//获取主题的accentColor
color: Theme.of(context).accentColor,
child: Text(
'带有背景颜色的文本组件',
//获取主题的文本样式
style: Theme.of(context).textTheme.title,
),
),
),
floatingActionButton: Theme(
//使用copyWith的方式获取accentColor
data: Theme.of(context).copyWith(accentColor: Colors.grey),
child: FloatingActionButton(
onPressed: null,
child: Icon(Icons.computer),
),
),
);
}
}
2.3 使用包资源
Flutter包就是flutter插件。类似于jar包。是世界上优秀的开源软件工作者创造的轮子,访问pub.dev网站可以搜索自己需要的轮子。
比如路由处理(fluro),网页跳转(url_launcher),第三方平台SDK(Firebase)。
第3章 Dart语言简述
Dart作为Flutter SDK指定的语言,我们需要了解Dart语言的基础知识,包括语法特性,基本语句,面向对象。
3.1 Dart重要概念那和常用开发库
Dart的声明式变成布局已有阅读和可视化看,因此不再需要单独的声明式布局语言XML和可视化界面构建器。这让界面渲染的速度更快。 Dart的重要概念如下:
- Dart代码在运行前解析。指定数据类型和编译时的常量,可以提高运行速度。
- Dart是弱类型的的语言。
- Dart类似于Java中的一切皆对象。
- Dart程序有统一的函数入口,main()。
- Dart没有public,protected和private的该你那。私有特性通过变量或函数加上下划线来表示。
- Dart的warning不妨碍程序运行,errors,可以是斌阿姨时候的错误,也可能是运行时候的村务。
- Dartzhichi async、await异步处理
- Dart有关键字56个;
Dart语言常用库如下:
包名 描述
dart:async 异步编程支持,提供Future和Stream类
dart:collection 对dart:core提供更多的集合支持
dart:convert 不同类型(JSON,UTF-8)间的字符编码、解码支持
dart:core Dart语言内建的类型、对象以及dart语言核心的功能
dart:html 网页开发用到的库
dart:io 文件读写I/O相关操作的库
dart:math 数字常量及函数,提供随机数算法
dart:svg 事件和动画的矢量图像支持
以下三个库的使用频率最高
①.dart:core:核心库,包括strings、numbers、collections、errors、dates、URIs等
②.dart:html:网页开发里DOM相关的一些库
③.dart:io:I/O命令行使用的I/O库
dart:core库是Dart语言初始已经包含的库,其他的任何库在使用前都需要加上import语句。
3.2 变量与基本数据类型
Dart中的变量用var关键字声明,如下:
var name = '靓哥'
变量如果没有初始化,那么默认是空;
3.2.1.常量和固定值
final或者const来声明常量。const是一个编译时常量,final的值只能赋值一次.
Dart中const与final区别:如果常量是编译期就能初始化的就用const;如果常量是运行时期初始化的就用final。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(顺序不能颠倒)
3.2.2.基本数据类型
| 语言 | Dart | OC |
|---|---|---|
| 数字类型 | num、int、double | int、float、double、long long、NSInteger、NSNumber、NSValue |
| 布尔类型 | bool(true、false) | bool、BOOL |
| 字符串 | String:单双引号均可定义String nickname = 'Bobby'; | NSString、NSMutableStringNSString *nickname = @”Bobby”; |
| 数组 | List:Dart 在 2.3 引入了扩展操作符(...)和 空感知扩展操作符(...?),它们提供了一种将多个元素插入集合的简洁方法Dart 还同时引入了 集合中的if 和 集合中的 for 操作 | NSArray、NSMutableArray |
| 集合 | Setvar halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'}; | NSSet、NSMutableSet |
| 字典 | Mapvar gifts = Map<String, String>();gifts['first'] = 'partridge'; | NSDictionary、NSMutableDictionary |
| 枚举类型 | enum Color { red, green, blue } | enum、NS_ENUM、NS_OPTIONS |
| 动态类型 | dynamic: Indicates that you want to disablestatic checking. Usually you should use Object or Object? instead. | id |
| 类型定义符 | typedef | typedef |
| 变量 | var(根据初始化赋值推断数据类型) | 必须声明时指定数据(对象)类型 |
| 常量 | const、final | const |
| 类型判断 | is、is! | isKindOfClass、isMemberOfClass |
| 类型转换 | as | (类型)、__bridge |
| 数据输出 | int num = 1;print("整数类型 num: $ {num} | int num = 1;NSLog(@"整数类型 num:%@", @(num)) |
| 基类 | Object | NSObjcet |
| 函数类型 | Function | 无 |
| 其他 | runes、symbol、late(变量首次使用的时候初始化) |
3.2.3.详细的讲讲基本数据类型
(1).Number类型
包含java中的int整形和double浮点型
(2).String类型
可以使用三个单引号或者双引号来定义多行的String类型,在Flutter中专门用三个引号表示大文本块。示例代码如下:
var s1 = '''
请注意这是一个用三个单引号包裹起来的字符串,
可以用来添加多行数据。换成三个双引号也是一样的。
双引号是“这样的。
‘’‘
(3).Boolean类型
Dart是强bool类型检查,只有bool类型的值是true才是true,所以java中的if(1>0)这样的判断是不支持的。
(4).List类型
Dart里面的List是具有相同类型的一组数据。类似java中被泛型限定的集合。或者说,就像是Java里面的数组。示例如下:
var list = [1,2,3];
List对象的第一个元素的索引是0,最后一个元素的索引是list.length-1.
(5).Map类型
Map是键值对,Key必须是唯一的。如下示例是一个Map,分别是数组方式和构造方式创建Map对象:
var week = {
'Monday' : '星期一',
'Tuesday': '星期二',
'Wednesday' : '星期三',
'Thursday': '星期四',
'Friday': '星期五',
'Saturday': '星期六',
'Sunday': '星期日',
};
var week = new Map();
week['Monday'] = '星期一';
week['Tuesday'] = '星期二';
week['Wednesday'] = '星期三';
week['Thursday'] = '星期四';
week['Friday'] = '星期五';
week['Saturday'] = '星期六';
week['Sunday'] = '星期日';
week['0'] = '星期一';
3.3 函数
函数也是对象,每个函数实例属于Function类型的对象。函数可以作为参数传递给其他函数。便于回调处理。
3.3.1.可选参数
将函数中的参数使用中括号括起来,用来表明是可选位置参数。例如,总共传入了三个参数,其中name和sex是必须传入的参数,from参数可以不传,代码如下:
//获取用户信息
String getUserInfo(String name, String sex, [String from]) {
var info = '$name的性别是$sex';
if (from != null) {
info = '$info来自$from';
}
return info;
}
void test() {
print(getUserInfo('小王', '男'));
}
调用上面的test方法输出”小王的性别是男“,但是不会输出来自哪里
3.3.2.参数默认值
如果参数指定了默认值,当不传入值时,函数会使用这个默认值。如果传入了值,会使用传入的值取代默认值。通常参数的默认值为null。示例如下:
String getUserInfo(String name, String sex, [String from = '中国']) {
var info = '$name的性别是$sex';
if (from != null) {
info = '$info来自$from';
}
return info;
}
void test(){
print(getUserInfo('小王', '男'));
}
调用上面的test方法输出”小王的性别是男来自中国“
3.3.3.函数的返回值
在Dart中,函数的返回值有如下特点:
- 所有的函数都会有返回值
- 如果没有指定函数返回值,则默认的返回值是null
- 没有返回值的函数,系统会在最后添加隐式的return语句
3.4 运算符
所有语言运算符都是差不多的。首先运算符肯定有优先级,优先级的问题通过括号可以解决,不建议用脑力记忆。
运算符也都是差不多,dart就是多了些。有如下几个:
3.4.1. 算术运算符
+,-,*,/,~/,%,++,--
3.4.2. 关系运算符
==,!=,>,<,<=,>=,
3.4.3. 类型测试运算符
| 操作符 | 含义 | Java中 |
|---|---|---|
| as | 类型转换 | 无 |
| is | 当对象是相应类型时返回true | instance of |
| is! | 当对象不是相应类型时返回true | !instance of |
3.4.4. 赋值运算符
要仅在比那辆为null时候赋值,使用??=运算符,示例如下:
//赋值给a
a= value;
//如果b为空,则将值分配给b;否则,b保持不变
b ?? = a;
赋值运算符包括复合赋值运算符局,将操作与赋值相结合和。+=,-=,这种,和java一样。
3.4.5. 逻辑运算符
| 操作符 | 含义 | Java中 |
|---|---|---|
| !expr | 反转一下表达式 | same |
| && | 逻辑或 | same |
| && | 逻辑与 | same |
逻辑或'||'符号怎么通过markdown表格绕过检查呢?大家懂就好
3.4.6. 条件表达式
Dart可以用if,else,也可以通过三元表达式。还有简介版本的三元表达式
expr1 ?? expr2,如果expr1为非空,返回expr1,否则返回expr2的表达式结果。
3.4.7. 级联操作
类似java里面的Build模式,级联通过两个点(..)表示,对同一个对象一直调用方法。
3.5 流程控制语句
和java一样,也是如下这些控制语句:
- if和else
- for
- while和do-while
- break和continue
- switch和case
Dart中switch/case语句使用==操作来比较整数,字符串或其他编译过程中的常量,从而实现分支的作用。switct/case语句的前后操作数必须是相同类型的对象实例。每个非空的case字句最后都必须跟上breaak语句。和java差不多。
- assert
断言,如果执行判断是false则发生中断。
- try-catch和throw
3.6 异常处理
Dart可以抛出并捕获异常,但是和java不同处,Dart的所有异常都是未检查的异常。方法不声明他们可能抛出哪些异常,也不需要捕获任何异常。神奇的是,Dart程序可以抛出任何非空对象。如下:
throw '数据为空'
稳健的程序一定是做了大量的异常处理,所以用Dart写程序,需要我们尽量考虑到可能发生的异常。这有点像是垃圾的JS。排查问题挨个断点了。
Dart的异常处理也是try-catch-finally
3.7 面向对象
Dart支持基于mixin的继承方式,就是多继承。 Dart的类中所有的变量都会隐式的定义setter方法,针对非空的变量会额外增加getter方法。
3.7.1.构造函数
Dart中有命名的构造函数,类似User.fromJson(Map json){name = json['name']},从另一个类或者现有数据中快速实现构造函数。
Dart中有构函数初始化列表,类似 User(name,age):name = name,age=age;,当调用new User('hello',20)时候,会更快的完成实例的成员变量的赋值。
3.7.2.读取和写入对象
get()和set()方法专门用于读取和写入对象的属性。每一个类的实例,系统都会隐式地包含有get()和set方法。里一个例子如下:
class Rectangle {
num left;
num top;
num width;
num height;
Rectangle(this.left, this.top, this.width, this.height);
//获取right值
num get right => left + width;
//设置right值 同时left也发生变化
set right(num value) => left = value - width;
//获取bottom值
num get bottom => top + height;
//设置bottom值 同时top也发生变化
set bottom(num value) => top = value - height;
}
main() {
var rect = new Rectangle(3, 4, 20, 15);
print('left:'+rect.left.toString());
print('right:'+rect.right.toString());
rect.right = 30;
print('更改right值为30');
print('left:'+rect.left.toString());
print('right:'+rect.right.toString());
print('top:'+rect.top.toString());
print('bottom:'+rect.bottom.toString());
rect.bottom = 50;
print('更改bottom值为50');
print('top:'+rect.top.toString());
print('bottom:'+rect.bottom.toString());
}
3.7.3.重载操作
Dart中可以重载算术运算符,比如某个类中重载+,类对象调用+,将会按照重载方法执行
//定义一个向量类
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);
//重载加号 + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}
//重载减号 - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}
main() {
//实例化两个向量
final v = new Vector(2, 3);
final w = new Vector(2, 2);
final r1 = v + w;
print('r1.x='+r1.x.toString() + ' r1.y=' + r1.y.toString());
final r2 = v - w;
print('r2.x='+r2.x.toString() + ' r2.y=' + r2.y.toString());
}
3.7.4.继承类
和java一样,通过extends和super实现继承。
3.7.5.抽象类
Dart中的抽象类相当于java中的接口。抽象类里没有具体实现方法。使用abstract关键字。
3.7.6.枚举类型
和java里面的枚举一样的,也是用enum关键字
3.7.7.Mixins
Mixins是多继承,也就是说使用这个关键字可以实现继承多个类。使用with关键字来实现Mixins的功能。这个概念比较复杂,建议单独从网上找一篇博客看看。我简要说一下,就是使用类似如下的方式:
class T = A with S;
则声明了一个T类,T类同时继承了A类和S类了。创建T类的实例就可以调用A类和S类的方法了。
3.8 泛型
Dart中使用<'T'>的方式来定义泛型,作用的地方如下:
3.8.1.用于集合类型
List和Map,例子如下:
var names = <'String'> ['a','b','c'];
var weeks = <String,String> {}
3.8.2.在构造函数中参数化
var user = new Map<String,User>();
3.9 库的使用
3.9.1.引用库
通过import语句在一个库中引用另一个库的文件。注意如下:
- 在import语句后面需要接上库文件的路径。
- 对Dart语言提供的库文件使用dart:xx格式。
- 对第三方的库文件使用package:xx格式。
import 'dart.io';
import 'pakcage:mylib/mylib.dart';
3.9.2.通过as指定一个库的前缀
对于两个库有相同的类,则可以给某个库设置一个别名
import 'pakcage:mylib/mylib.dart’;
import 'pakcage:mylib2/mylib.dart' as lib2;
3.9.3.引用库的一部分
如果只需要使用库的一部分内容,可以通过show和hide关键字实现有选择的引用。
- show关键字:只引用一点
- hide关键字:除此之外都引用。
//只导入foo
import 'pakcage:mylib/mylib.dart’ show foo;
//除了foo都导入
import 'pakcage:mylib/mylib.dart’ hide foo;
代码中的第一行只引用mylib.dart下的foo方法,第二行代码则除了foo方法,都导入了。
3.10 异步支持
使用async函数和await表达式实现异步操作。 Dart库给提供asynchronous功能,该功能提供接口来进行耗费时间的操作,比如文件读写,网络请求。返回值为Future或者Stream对象。
async和await总是成对出现,必须在一个使用了async关键字标记的函数中使用await表达式,示例如下:
fileOperate() async {
//读取文件
var file = await readFile();
//其他处理
}
3.11 元数据
元数据是以@开始的修饰符 ,目前Dart提供三个@修饰符: @deprecated 被弃用的 @override 重写 @proxy 代理 元数据可以修饰libraray,class,typedef,type parameter,constructor,factory,function,field,parameter,variable declaration。
就是一个功能不够丰富的java注解。
3.12 注释
Dart支持三种注释类型:
- 单行注释 //
- 多行注释 /* */
- 文档注释 /**或者///开头