读书笔记–《flutter实战》
Flutter实战
Dart
数据类型声明
变量
var
类似于JavaScript中的var,它可以接收任何类型的变量,但最大的不同是Dart中var变量一旦赋值,类型便会确定,则不能再改变其类型
Object,dynamic
Object 是Dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象. dynamic与var一样都是关键词,声明的变量可以赋值任意对象。 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型。dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。如:
常量
final,const
如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。 一个 final 变量只能被设置一次,两者区别在于:const 变量是一个编译时常量,final变量在第一次使用时被初始化。被final或者const修饰的变量,变量类型可以省略简单来说,两者定义的数据都是不可更改的,但final可以后确定,例如在构造方法中赋值,而const一开始定义的时候就必须赋值了。
变量或常量的名称由"_“开头,表示私有,既private
函数
Dart里的函数是可以赋值给变量,并进行传递的。Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。注意点:Dart函数声明如果没有显式声明返回值类型时会默认做dynamic处理,注意,函数返回值没有类型推断:typedef bool CALLBACK(); //不指定返回类型,此时默认为dynamic,不是bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] != null; } void test(CALLBACK cb){ print(cb()); } //报错,isNoble不是bool类型 test(isNoble);
可选参数写法
可选的位置参数包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:String say(String from, String msg, [String device]) { var result = ‘$from says KaTeX parse error: Expected '}', got 'EOF' at end of input: … result = 'result with a KaTeX parse error: Expected 'EOF', got '}' at position 12: device'; }̲ return resul…{expression}将表达式的值放在字符串中。如果表达式是标识符,则可以跳过{}使用带有单引号或双引号的三引号创建多行字符串:var s1 = ‘’’ You can create multi-line strings like this one. ‘’’; var s2 = “”“This is also a multi-line string.””"; 可以通过在前面加上r来创建“原始”字符串:var s = r’In a raw string, not even \n gets special treatment.’; //输出s,会发现原本的换行符号成了普通字符输出了
分支主题
标签来控制流程
void main() { outerloop: for (var i = 0; i < 5; i++) { print(“Innerloop: ${i}”); innerloop: for (var j = 0; j < 5; j++) { if (j > 3 ) break ; if (i == 2) break innerloop; if (i == 4) break outerloop; print(“Innerloop: ${j}”); } } }
集合
for…invoid main() { var obj = [12,13,14]; for (var prop in obj) { print(prop); } } 使用扩展运算符(…)将列表的所有元素插入另一个列表:var list = [1, 2, 3]; var list2 = [0, …list]; assert(list2.length == 4); 如果扩展运算符右侧的表达式可能为null,则可以通过使用支持null的扩展运算符(…?)来避免异常:var list; var list2 = [0, …?list]; assert(list2.length == 1);
设置App图标
Android
在Flutter项目的根目录中,导航到…/android/app/src/main/res目录,里面包含了各种资源文件夹(如mipmap-hdpi已包含占位符图像“ic_launcher.png”,见图2-8)。 只需按照Android开发人员指南中的说明, 将其替换为所需的资源,并遵守每种屏幕密度(dpi)的建议图标大小标准。
ios
在Flutter项目的根目录中,导航到…/ios/Runner。该目录中Assets.xcassets/AppIcon.appiconset已经包含占位符图片(见图2-9), 只需将它们替换为适当大小的图片,保留原始文件名称。
设置闪屏页面
Android
要将启动屏幕(splash screen)添加到您的Flutter应用程序, 请导航至…/android/app/src/main。在res/drawable/launch_background.xml,通过自定义drawable来实现自定义启动界面(你也可以直接换一张图片)。
ios
要将图片添加到启动屏幕(splash screen)的中心,请导航至…/ios/Runner。在Assets.xcassets/LaunchImage.imageset, 拖入图片,并命名为LaunchImage.png、LaunchImage@2x.png、LaunchImage@3x.png。 如果你使用不同的文件名,那您还必须更新同一目录中的Contents.json文件,图片的具体尺寸可以查看苹果官方的标准。您也可以通过打开Xcode完全自定义storyboard。在Project Navigator中导航到Runner/Runner然后通过打开Assets.xcassets拖入图片,或者通过在LaunchScreen.storyboard中使用Interface Builder进行自定义,
路由管理
普通路由
跳转: Navigator.push( context, MaterialPageRoute(builder: (context) { return NewRoute(); })); 带参数跳转: var result = await Navigator.push( context, MaterialPageRoute( builder: (context) { return TipRoute( // 路由参数 text: “我是提示xxxx”, ); }, ), ); //输出TipRoute
路由返回结果 print(“路由返回值: $result”); 或者可以写成: Navigator.push( context, MaterialPageRoute( builder: (context) { return TipRoute( // 路由参数 text: “我是提示xxxx”, ); }, ), ).then((value) => { //输出TipRoute
路由返回结果 print(“路由返回值: $value”);}); 退出:Navigator.pop(context, “我是返回值”)
命名路由
注册路由表://在MaterialApp中做注册 routes:{ “/”:(context) => RandomWords(), //注册首页路由 “NewRoute”:(context) => NewRoute(), }, 带参数跳转:Navigator.of(context).pushNamed(“NewRoute”, arguments: _saved).then((value) => print(value)); 接受参数:Set _saved=ModalRoute.of(context).settings.arguments;
(onGenerateRoute)命名路由跳转控制
//在MaterialApp中配置 //只有没有在路由表中的命名路由跳转时才会回调该方法,既没有在 //MaterialApp中注册过的 onGenerateRoute: (RouteSettings settings){ return MaterialPageRoute(builder: (context){ String routeName = settings.name; } ); },
参数传递
可用构造方法传递参数,也可通过settings.arguments进行参数的传递
资源管理
Flutter APP安装包中会包含代码和 assets(资源)两部分。Assets是会打包到程序安装包中的,可在运行时访问。常见类型的assets包括静态数据(例如JSON文件)、配置文件、图标和图片(JPEG,WebP,GIF,动画WebP / GIF,PNG,BMP和WBMP)等。
指定资源
和包管理一样,Flutter也使用pubspec.yaml文件来管理应用程序所需的资源,举个例子:flutter: assets: - assets/my_icon.png - assets/background.png assets指定应包含在应用程序中的文件, 每个asset都通过相对于pubspec.yaml文件所在的文件系统路径来标识自身的路径。asset的声明顺序是无关紧要的,asset的实际目录可以是任意文件夹(在本示例中是assets文件夹)。在构建期间,Flutter将asset放置到称为 asset bundle 的特殊存档中,应用程序可以在运行时读取它们(但不能修改)。
加载资源
加载文本assets通过rootBundle 对象加载:每个Flutter应用程序都有一个rootBundle对象, 通过它可以轻松访问主资源包,直接使用package:flutter/services.dart中全局静态的rootBundle对象来加载asset即可。通过 DefaultAssetBundle 加载:建议使用 DefaultAssetBundle 来获取当前BuildContext的AssetBundle。 这种方法不是使用应用程序构建的默认asset bundle,而是使父级widget在运行时动态替换的不同的AssetBundle,这对于本地化或测试场景很有用。通常,可以使用DefaultAssetBundle.of()在应用运行时来间接加载asset(例如JSON文件),而在widget上下文之外,或其它AssetBundle句柄不可用时,可以使用rootBundle直接加载这些asset,例如:import ‘dart:async’ show Future; import ‘package:flutter/services.dart’ show rootBundle; Future<String> loadAsset() async { return await rootBundle.loadString(‘assets/config.json’); } 加载图片类似于原生开发,Flutter也可以为当前设备加载适合其分辨率的图像。声明分辨率相关的图片 assetsAssetImage 可以将asset的请求逻辑映射到最接近当前设备像素比例(dpi)的asset。为了使这种映射起作用,必须根据特定的目录结构来保存asset:…/image.png…/Mx/image.png…/Nx/image.png…etc.其中M和N是数字标识符,对应于其中包含的图像的分辨率,也就是说,它们指定不同设备像素比例的图片。主资源默认对应于1.0倍的分辨率图片。看一个例子:…/my_icon.png…/2.0x/my_icon.png…/3.0x/my_icon.png在设备像素比率为1.8的设备上,…/2.0x/my_icon.png 将被选择。对于2.7的设备像素比率,…/3.0x/my_icon.png将被选择。如果未在Image widget上指定渲染图像的宽度和高度,那么Image widget将占用与主资源相同的屏幕空间大小。 也就是说,如果…/my_icon.png是72px乘72px,那么…/3.0x/my_icon.png应该是216px乘216px; 但如果未指定宽度和高度,它们都将渲染为72像素×72像素(以逻辑像素为单位)。pubspec.yaml中asset部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到高的顺序去选择 ,也就是说1x中没有的话会在2x中找,2x中还没有的话就在3x中找。加载图片要加载图片,可以使用 AssetImage类。例如,我们可以从上面的asset声明中加载背景图片:Widget build(BuildContext context) { return new DecoratedBox( decoration: new BoxDecoration( image: new DecorationImage( image: new AssetImage(‘graphics/background.png’), ), ), ); } 注意,AssetImage 并非是一个widget, 它实际上是一个ImageProvider,有些时候你可能期望直接得到一个显示图片的widget,那么你可以使用Image.asset()方法,如:Widget build(BuildContext context) { return Image.asset(‘graphics/background.png’); } 使用默认的 asset bundle 加载资源时,内部会自动处理分辨率等,这些处理对开发者来说是无感知的。 (如果使用一些更低级别的类,如 ImageStream或 ImageCache 时你会注意到有与缩放相关的参数)
State生命周期
reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。didUpdateWidget():在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。
initState
当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。
didChangeDependencies
当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。
build
build():此回调读者现在应该已经相当熟悉了,它主要是用于构建Widget子树的,会在如下场景被调用:在调用initState()之后。在调用didUpdateWidget()之后。在调用setState()之后。在调用didChangeDependencies()之后。在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。
deactivate
deactivate():当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
dispose
dispose():当State对象从树中被永久移除时调用;通常在此回调中释放资源。