06. Flutter杂项
pubspec.yaml
简单的 pubspec.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: flutter_in_action
description: First Flutter Application.
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
- name:应用或包名称。
- description: 应用或包的描述、简介。
- version:应用或包的版本号。
- dependencies:应用或包依赖的其他包或插件。
- dev_dependencies:开发环境依赖的工具包(而不是 flutter 应用本身依赖的包)。
- flutter:flutter 相关的配置选项。
需要注意 dependencies 和 dev_dependencies 的区别,前者的依赖包将作为 App 的源码的一部分参与编译,生成最终的安装包。而后者的依赖包只是作为开发阶段的一些工具包,主要是用于帮助我们提高开发、测试效率,比如 flutter 的自动化测试包等。
dependencies
引入第三方插件
1
2
3
4
5
6
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_easyrefresh: ^2.2.1
flutter pub get
更新依赖- VSCode 在检测到
pubspec.yaml
变化时会自动拉取
包管理
Pub 仓库
Pub(https://pub.dev/ )是 Google 官方的 Dart Packages 仓库,类似于 node 中的 npm 仓库、Android 中的 jcenter。我们可以在 Pub 上面查找我们需要的包和插件,也可以向 Pub 发布我们的包和插件
依赖
pub 仓库依赖
1
2
3
4
5
dependencies:
flutter:
sdk: flutter
# 新添加的依赖
english_words: ^4.0.0
依赖本地包
1
2
3
dependencies:
pkg1:
path: ../../code/pkg1
路径可以是相对的,也可以是绝对的。
依赖 Git
1
2
3
4
dependencies:
pkg1:
git:
url: git://github.com/xxx/pkg1.git
不在 git 仓库根目录,可以使用 path 参数指定相对位
1
2
3
4
5
dependencies:
package1:
git:
url: git://github.com/flutter/packages.git
path: packages/package1
其他的依赖方式
https://www.dartlang.org/tools/pub/dependencies
资源管理
常见类型的 assets 包括静态数据(例如 JSON 文件)、配置文件、图标和图片等。
指定 assets
1
2
3
4
flutter:
assets:
- assets/my_icon.png
- assets/background.png
assets 指定应包含在应用程序中的文件, 每个 asset 都通过相对于 pubspec.yaml 文件所在的文件系统路径来标识自身的路径。asset 的声明顺序是无关紧要的,asset 的实际目录可以是任意文件夹(在本示例中是 assets 文件夹)。
在构建期间,Flutter 将 asset 放置到称为 asset bundle 的特殊存档中,应用程序可以在运行时读取它们(但不能修改)。
Asset 变体(variant)
例如,如果应用程序目录中有以下文件:
- …/pubspec.yaml
- …/graphics/my_icon.png
- …/graphics/background.png
- …/graphics/dark/background.png
在 pubspec.yaml 文件中只需包含:
1
2
3
flutter:
assets:
- graphics/background.png
那么这两个 graphics/background.png
和 graphics/dark/background.png
都将包含在您的 asset bundle 中。前者被认为是 main asset (主资源),后者被认为是一种变体(variant)。
在选择匹配当前设备分辨率的图片时,Flutter 会使用到 asset 变体
加载 Asset
应用可以通过 AssetBundle 对象访问其 asset 。有两种主要方法允许从 Asset bundle 中加载字符串或图片(二进制)文件
加载文本 assets
- 通过 rootBundle 对象加载:每个 Flutter 应用程序都有一个对象, 通过它可以轻松访问主资源包,直接使用 package:flutter/services.dart 中全局静态的 rootBundle 对象来加载 asset 即可。
- 通过 DefaultAssetBundle 加载:建议使用 DefaultAssetBundle 来获取当前 BuildContext 的 AssetBundle。 这种方法不是使用应用程序构建的默认 asset bundle,而是使父级 widget 在运行时动态替换的不同的 AssetBundle,这对于本地化或测试场景很有用。
通常,可以使用 DefaultAssetBundle.of() 在应用运行时来间接加载 asset(例如 JSON 文件),而在 widget 上下文之外,或其他 AssetBundle 句柄不可用时,可以使用 rootBundle 直接加载这些 asset,例如:
1
2
3
4
5
6
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
Future<String> loadAsset() async {
return await rootBundle.loadString('assets/config.json');
}
加载图片
声明分辨率相关的图片 assets
AssetImage 可以将 asset 的请求逻辑映射到最接近当前设备像素比例(dpi)的 asset。为了使这种映射起作用,必须根据特定的目录结构来保存 asset:
- …/image.png
- …/Mx/image.png
- …/Nx/image.png
- …
其中 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 类
1
2
3
4
5
6
7
8
9
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('graphics/background.png'),
),
),
);
}
注意,AssetImage 并非是一个 widget, 它实际上是一个 ImageProvider,有些时候你可能期望直接得到一个显示图片的 widget,那么你可以使用 Image.asset() 方法,如:
1
2
3
Widget build(BuildContext context) {
return Image.asset('graphics/background.png');
}
使用默认的 asset bundle 加载资源时,内部会自动处理分辨率等,这些处理对开发者来说是无感知的。 (如果使用一些更低级别的类,如 ImageStream 或 ImageCache 时你会注意到有与缩放相关的参数)
依赖包中的资源图片
要加载依赖包中的图像,必须给 AssetImage 提供 package 参数。
假设您的应用程序依赖于一个名为 “my_icons” 的包,它具有如下目录结构:
- …/pubspec.yaml
- …/icons/heart.png
- …/icons/1.5x/heart.png
- …/icons/2.0x/heart.png
加载图片代码:
1
2
3
AssetImage('icons/heart.png', package: 'my_icons')
// 或者
Image.asset('icons/heart.png', package: 'my_icons')
注意:包在使用本身的资源时也应该加上 package 参数来获取。
打包包中的 assets
如果在 pubspec.yaml 文件中声明了期望的资源,它将会打包到相应的 package 中。特别是,包本身使用的资源必须在 pubspec.yaml 中指定。
包也可以选择在其 lib/文件夹中包含未在其 pubspec.yaml 文件中声明的资源。在这种情况下,对于要打包的图片,应用程序必须在 pubspec.yaml 中指定包含哪些图像。
例如,一个名为 “fancy_backgrounds” 的包,可能包含以下文件:
- …/lib/backgrounds/background1.png
- …/lib/backgrounds/background2.png
- …/lib/backgrounds/background3.png
要包含第一张图像,必须在 pubspec.yaml 的 assets 部分中声明它:
1
2
3
flutter:
assets:
- packages/fancy_backgrounds/backgrounds/background1.png
lib/
是隐含的,所以它不应该包含在资产路径中。
特定平台 assets
App 图标
- Android:
…/android/app/src/main/res/xxx-dpi/ic_launcher.png
- iOS:
…/ios/Runner/Assets.xcassets/AppIcon.appiconset
启动页
- Android:
…/android/app/src/main/res/drawable/launch_background.xml
- iOS:导航至
…/ios/Runner
。在Assets.xcassets/LaunchImage.imageset
, 拖入图片,并命名为LaunchImage.png
、LaunchImage@2x.png
、LaunchImage@3x.png
平台共享 assets
如果我们采用的是 Flutter+ 原生的开发模式,那么可能会存 Flutter 和原生需要共享资源的情况,比如 Flutter 项目中已经有了一张图片 A,如果原生代码中也要使用 A,我们可以将 A 拷贝一份到原生项目的特定目录,这样的话虽然功能可以实现,但是最终的应用程序包会变大,因为包含了重复的资源,为了解决这个问题,Flutter 提供了一种 Flutter 和原生之间共享资源的方式,见 官方文档。
Flutter 调试和性能
调试
flutter attach
性能
Timeline 工具
DevTools 工具
Flutter DevTools 是 Flutter 可视化调试工具,它将各种调试工具和能力集成在一起,并提供可视化调试界面,它的功能很强大
Flutter 异常
- 同步异常:可以通过
try/catch
捕获;默认异常处理逻辑 FlutterError.onError - 异步异常:
runZoned(…)
,类似沙盒运行环境
1
2
3
4
5
try {
Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
} catch (e) {
print(e)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void collectLog(String line){
... //收集日志
}
void reportErrorAndLog(FlutterErrorDetails details){
... //上报错误和日志逻辑
}
FlutterErrorDetails makeDetails(Object obj, StackTrace stack){
...// 构建错误信息
}
void main() {
var onError = FlutterError.onError; //先将 onerror 保存起来
FlutterError.onError = (FlutterErrorDetails details) {
onError?.call(details); //调用默认的onError
reportErrorAndLog(details); //上报
};
runZoned(
() => runApp(MyApp()),
zoneSpecification: ZoneSpecification(
// 拦截print
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
collectLog(line);
parent.print(zone, "Interceptor: $line");
},
// 拦截未处理的异步错误
handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,
Object error, StackTrace stackTrace) {
reportErrorAndLog(details);
parent.print(zone, '${error.toString()} $stackTrace');
},
),
);
}
主题和文字
如何在 Text Widget 上设置自定义字体?
只需要在文件夹中放置字体文件,并在 pubspec.yaml
中引用它
1
2
3
4
5
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
然后在 Text widget 添加 style: TextStyle(fontFamily: 'MyCustomFont')
如何给 App 设置主题?
- Material Design 主题:声明一个顶级 Widget,
MaterialApp
是一个便利的组件,包含了 App 很多需要的 MD 风格组件。 - Cupertino Widget 实现 iOS 效果