文章

06. Flutter杂项

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.pnggraphics/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 加载资源时,内部会自动处理分辨率等,这些处理对开发者来说是无感知的。 (如果使用一些更低级别的类,如 ImageStreamImageCache 时你会注意到有与缩放相关的参数)

依赖包中的资源图片

要加载依赖包中的图像,必须给 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 图标

  1. Android:…/android/app/src/main/res/xxx-dpi/ic_launcher.png
  2. iOS:…/ios/Runner/Assets.xcassets/AppIcon.appiconset

启动页

  1. Android:…/android/app/src/main/res/drawable/launch_background.xml
  2. iOS:导航至 …/ios/Runner。在 Assets.xcassets/LaunchImage.imageset, 拖入图片,并命名为 LaunchImage.pngLaunchImage@2x.pngLaunchImage@3x.png

平台共享 assets

如果我们采用的是 Flutter+ 原生的开发模式,那么可能会存 Flutter 和原生需要共享资源的情况,比如 Flutter 项目中已经有了一张图片 A,如果原生代码中也要使用 A,我们可以将 A 拷贝一份到原生项目的特定目录,这样的话虽然功能可以实现,但是最终的应用程序包会变大,因为包含了重复的资源,为了解决这个问题,Flutter 提供了一种 Flutter 和原生之间共享资源的方式,见 官方文档

Flutter 调试和性能

https://book.flutterchina.club/chapter2/flutter_app_debug.html#_2-7-2-%E8%B0%83%E8%AF%95%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E5%B1%82

调试

flutter attach

性能

Timeline 工具

DevTools 工具

Flutter DevTools 是 Flutter 可视化调试工具,它将各种调试工具和能力集成在一起,并提供可视化调试界面,它的功能很强大

Flutter 异常

  1. 同步异常:可以通过 try/catch 捕获;默认异常处理逻辑 FlutterError.onError
  2. 异步异常: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 效果
本文由作者按照 CC BY 4.0 进行授权