您的位置:首页 > 产品设计 > UI/UE

Flutter第一部分(UI)第六篇:一文搞懂Flutter中的资源引用机制

2019-06-11 19:18 579 查看
版权声明:本文为ai-exception原创作品,如有转载请注明出处 https://blog.csdn.net/qq_36982160/article/details/91457264

前言:Flutter系列的文章我应该会持续更新至少一个月左右,从User Interface(UI)到数据相关(文件、数据库、网络)再到Flutter进阶(平台特定代码编写、测试、插件开发等),欢迎感兴趣的读者持续关注(可以扫描左边栏二维码或者微信搜索”IT工匠“关注微信公众号哦,会同步推送)。

Flutter
应用程序可以包括代码(
code
)和资产(
asset
),有时也将资产称为资源(
resource
),由于大多数人比较习惯资源的概念,本文如果不加说明,都将
asset
称为资源
。 资源中包含的文件会与你的应用程序绑定在一起最后部署到宿主机上,可在软件运行期间访问这部分文件。 常见的资源类型包括静态数据(例如
JSON
文件),配置文件图标和图像
JPEG
WebP
GIF
动画WebP / GIF
PNG
BMP
WBMP
)等。

资源的注册(指定)

Flutter
使用位于项目根目录下的
pubspec.yaml
文件来标识
app
所需要的资源,如下所示:

flutter:
assets:
- assets/my_icon.png
- assets/background.png

上面的代码将

assets
目录下的
my_icon.png
background.png
文件进行了注册(指定),这样我们就可以直接在代码中访问到这两个文件,有时我们可能有很多个资源文件需要在
pubspec.yaml
文件中指定,这时如果像上面代码那样一个文件一个文件注册就显得有点麻烦,我们可以使用以
/
字符结尾的格式将整个文件夹下的文件同时指定进来,就像下面这样:

flutter:
assets:
- assets/

注意,上面的代码会将

assets
目录下的所有直接子文件都指定进来,但是
assets
下的子文件夹下的文件(即间接子文件)是不会被指定进来的,需要对这些间接子文件或者子文件夹进行再次指定。

其实说白了,就是将代码中需要访问的资源文件在

pubspec.yaml
文件中进行一个注册,这样就能通过
pubspec.yaml
文件的注册信息定位到最终需要的文件进而实现访问
,比如,假设现在文件的目录结构如下:

pubspec.yaml
文件的声明如下:

flutter:
assets:
- assets/

这样我们只能访问到

assets
的直接子文件
flutter_blue.png
,不能访问到
assets
的子目录
child
中的文件
flutter_red.png
,如果想要访问,可以将
pubspec.yaml
文件更新为:

flutter:
assets:
- assets/- assets/child/
#或者:- assets/child/flutter_blue.png

这样既可以访问到文件

flutter_blue.png
了。

注意:在

flutter
项目的
pubspec.yaml
文件中指定资源时,
assets
节点下的目录名是没有限制的,这个实例中用的是
assets
,你也可以根据自己的需要使用其他目录,只要保证使用的目录位置再项目的根目录下即可,比如:

flutter:
assets:
- images/

将资源文件夹命名为

images
,这也是完全可以的。

资源的打包

pubspec.yaml
文件中
flutter
节下的
assets
子节中指定的文件会包含在最终的
app
内(即一起被打包),每一个资源文件的位置都会以一个确定的路径(相对于
pubspec.yaml
的相对路径)来进行标识。

app
的构建(
build
)期间,
Flutter
会将资源(
asset
)打包放入名为资源包(
asset bundle
)的特殊
存档中,应用程序可以在运行时读取这些存档

资源的自动版本管理

app
的构建进程可以自动对资源进行版本管理:可以在不同的上下文中使用资源的不同版本。 在
pubspec.yaml
assets
部分中指定资源的路径时,构建进程会查找相邻子目录中具有相同名称的所有文件,这些文件会与指定的资源一起打包进资源包中。

举个例子,假设你的项目的文件目录结构是这样的:

.../pubspec.yaml
.../assets/flutter_icon.png
.../assets/red_icon/flutter_icon.
.../assets/icon.png
...etc.

而你的

pubspec.yaml
文件中是这么声明的:

flutter:
assets:
- assets/flutter_icon.png

这样的话

assets/flutter_icon.png
assets/red/flutter_icon.png
文件都会被打包进你的资源包中,前者被认为是主版本,后者被认为是主版本的变体。

如果

pubspec.yaml
文件使用如下的声明方法:

flutter:
assets:
- assets/

这样

assets/icon.png
assets/flutter_icon.png
assets/red/flutter_icon.png
都会被包含进去。

Flutter
会根据不同的屏幕分辨率选择合适的图像(不同的资源版本)进行使用,这一点在下面我会再进行介绍。将来,这种机制可能还会扩展到根据宿主机所处的不同地区使用不同版本的资源、根据屏幕的方向(横屏还是竖屏)选择不同版本的资源等其他场景。

加载资源

我们可以在

app
中使用
AssetBundle
类的对象来访问资源中的文件,具体的,根据加载资源的类型,可以将资源的加载分为两类:

  • 加载资源中的
    string/text
    文件(通过
    AssetBundle.loadString(String key)
    方法)
  • 加载资源中的图像/二进制文件(通过
    AssetBundle.load(String key)
    方法)

这两种方法的使用前提是需要提供的

key
值,这里的
key
值指的是
pubspec.yaml
文件中注册(指定)的资源的路径,比如在
pubspec.yaml
文件中声明了:

flutter:
assets:
- assets/flutter_icon.png

那么如果想加载

flutter_icon.png
key
值就是
'assets/flutter_icon.png'

下面我们来分别介绍一下如何具体地加载文本资源文件和图像/二进制文件。

加载文本资源

我们刚才提到,加载资源文件需要借助

AssetBundle
类的对象,每一个
Flutter app
import 'package:flutter/services.dart'
包下都提供有一个实例化好的
AssetBundle rootBundle
对象,我们可以通过该对象访问资源包中的主资源,比如:

class _MyAppState extends State<MyApp> {
Image image;

_loadImage() async {
ByteData byteData = await rootBundle.load('assets/flutter_icon.png');
setState(() {
image = Image.memory(byteData.buffer.asUint8List());
});
}

@override
void initState() {
_loadImage();
super.initState();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Assets  demo',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('Assets demo'),
),
body: Center(
child: image,
),
),
);
}
}

这样就可以加载到我们需要的资源,但是请注意,通过

Flutter app
给我们实例化好的
rootBundle
对象加载的是默认的主资源,也就是说它是无法根据不同的上下文环境(比如不同的屏幕分辨率)加载不同版本的资源、它永远加载的都是默认的主资源中的资源,那么如何解决这个问题呢?

Flutter
官方推荐的做法是通过
DefaultAssetBundle
类结合当前的
BuildContext
来实例化与当前上下文环境相关联的
AssetBundle
对象,这样不同的上下文环境会实例化到不同的
AssetBundle
对象,就能保证程序在加载资源的时候根据不同的上下文环境动态决定加载最适合版本对应的资源,具体的做法分为以下两步:

  • 实例化
    AssetBundle
    类的对象
  • 加载资源

Talk is cheap ,show you my code

class _MyAppState extends State<MyApp> {
Image image;

_loadImage(AssetBundle assetBundle) async {
ByteData byteData = await assetBundle.load('assets/flutter_icon.png');
image = Image.memory(byteData.buffer.asUint8List());
}

@override
Widget build(BuildContext context) {
//这里通过DefaultAssetBundle和当前的上下文环境context实例化AssetBundle
AssetBundle assetBundle = DefaultAssetBundle.of(context);
_loadImage(assetBundle);
return MaterialApp(
title: 'Assets  demo',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('Assets demo'),
),
body: Center(
child: image,
),
),
);
}
}

以上介绍了两种加载资源文件的方法:

  • 第一种:直接使用
    Flutter app
    为我们实例化好的
    rootBundle
    对象
  • 第二种:使用
    DefaultAssetBundle
    context
    实例化自己的
    assetBundle
    对象

那么我们在实际的生产中什么时候应该使用第一种、什么时候应该使用第二种呢?答案是一般情况下我们应该尽可能地使用第二种方法,但是如果我们需要在

Widget
上下文之外加载资源,我们就无法获取当前环境的上下文
context
,这个时候就只能使用第一种方法了。

加载图像

Flutter
可以根据当前设备的分辨率加载合适的图像。

声明分辨率感知(resolution-aware)图像资源

我们可以通过

AssetImage
类将图像的加载请求自动映射到最接近当前设备像素比例的资源文件,当然这种做法的前提是使用特定的目录结构来保存图像资源,就像这样:

.../image.png
.../Mx/image.png
.../Nx/image.png
...etc.

其中

M
N
是数字标识符,代表对应文件夹下图像的分辨率等级,也就是说,它们指定了不同设备像素下应该加载的不同图片,主资源默认对应
1.0
倍的分辨率图片。比如,考虑如下名为
my_icon.png
的文件:

.../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
的时候没有指定渲染图像的宽度和高度,
Flutter
默认会使用标准分辨率来缩放图片资源,使其与主资源占用相同的屏幕空间。 也就是说,如果
.../my_icon.png
72px
72px
,那么
.../3.0x/my_icon.png
应该是
216px
216px
, 但如果未指定宽度和高度,它们都将渲染为
72px
72px
px
以逻辑像素为单位)。

pubspec.yaml
asset
部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到高的顺序去选择最接近自己标准的资源,比如如果需要
1x
,但是没找到,那么会去
2x
中找,
2x
中还没有的话就在
3x
中找,以此类推。

加载图片

要加载图片,我们应该在

Widget
build()
方法中使用
AssetImage
类。

例如:

Widget build(BuildContext context) {
// ...
return new DecoratedBox(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage('assets/icon.png'),
// ...
),
// ...
),
);
// ...
}

使用默认的资源包加载资源时,内部会自动处理分辨率,这些处理对开发者来说是不可见的、自动的, 如果你使用一些更靠近底层的类,比如

ImageStream
ImageCache
, 你就需要自己配置一些与缩放相关的参数。

依赖包(dependencies)中的资源图片

要加载依赖包(

dependencies
)中的图片,必须给
AssetImage
提供
package
参数。

比如,假设你的应用程序依赖于一个名为

“my_icons”
的包,它具有如下目录结构:

.../pubspec.yaml
.../icons/heart.png
.../icons/1.5x/heart.png
.../icons/2.0x/heart.png
...etc.

要加载该依赖包下的图像,你应该使用类似如下的代码:

new AssetImage('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
部分中声明它:

flutter:
assets:
- packages/fancy_backgrounds/backgrounds/background1.png

lib/
是隐含的,所以它不应该包含在资源路径中。

与特定平台共享资源

通过

Android
上的
AssetManager
iOS
上的
NSBundle
可以在编写特定平台代码的时候获取到
Flutter
中的资源。

Android

Android
中,可以通过
AssetManager
获得
Flutter
中的资源。具体的做法是使用
AssetManager.getopenFd(String key);
来获取对应资源,那么这个
key
应该是什么?答案是应该根据开发类型的不同来决定:

  • 如果开发的是
    Flutter
    插件,则应该使用
    PluginRegistry.Registrar.lookupKeyForAsset(assetPath)
    获取
  • 如果开发的是普通的
    app
    ,则应该使用
    FlutterView.getLookupKeyForAsset()
    获取

比如,假设你的

pubspec.yaml
文件中声明了如下代码:

flutter:
assets:
- icons/heart.png

而你

app
的包结构是这样的:

.../pubspec.yaml
.../icons/heart.png
...etc.

那么你可以使用类似如下

Java
代码访问到
icons/heart.png

AssetManager assetManager = registrar.context().getAssets();
String key = registrar.lookupKeyForAsset("icons/heart.png");
AssetFileDescriptor fd = assetManager.openFd(key);

ios

iOS
中,可以通过
mainBundle
获取资源。 用于
pathForResource:ofType:
key
可以有两种过获取方式:

  • 通过
    FlutterPluginRegistrar
    中的
    lookupKeyForAsset
    lookupKeyForAsset:fromPackage:
    获取
  • 通过
    FlutterViewController
    中的
    lookupKeyForAsset:
    lookupKeyForAsset:fromPackage:
    获取

同样应该根据开发类型的不同选择不同的资源获取方式:

  • 开发插件时可以使用
    FlutterPluginRegistrar
  • 开发普通
    Flutter app
    时使用
    FlutterViewController

比如你可以使用类似如下

Object-C
代码来访问
flutter
项目中的
icons/heart.png

NSString* key = [registrar lookupKeyForAsset:@"icons/heart.png"];
NSString* path = [[NSBundle mainBundle] pathForResource:key ofType:nil];

平台资源

还有时候可以直接在平台项目中使用资源,以下是在加载和运行

Flutter
框架之前使用资源的两种常见情况。

更新app 图标

更新

Flutter
应用程序的启动图标与在原生
Android
iOS
应用程序中更新启动图标的方式相同。

Android

Flutter
项目的根目录中,导航到
.../android/app/src/main/res
。各个位图资源文件夹(如
mipmap-hdpi
)已包含名为
ic_launcher.png
的占位符图像。 只需遵守
Android
官方文档中指示的不同屏幕像素密度推荐的图标大小原则,使用我们自己的图标替换掉
ic_launcher.png
即可:

iOS

在你的

Flutter
项目的根目录中,导航到
.../ios/Runner
,在目录
Assets.xcassets/AppIcon.appiconset
下已包含了占位符图像, 只需将它们替换为适当大小的图片即可:

更新启动页

Flutter
框架进行加载时,
Flutter
会使用本机平台机制将
app
启动页面绘制到屏幕上,这个启动页面会一直展示,直到
Flutter
呈现应用程序的第一帧。

注意: 这意味着如果你不在应用程序的

main()
方法中调用
runApp()
函数(底层原理其实是调用
window.render()
去响应
window.onDrawFrame()
)的话, 你的
app
启动后会永远启只显示启动页面。

Android

要在

Flutter
应用程序中添加启动画面,请在
.../android/app/src/main
。 在
res/drawable/launch_background.xml
文件中自定义你的启动背景,这样就可以达到更改启动页面的目的。

iOS

要将图片添加到启动屏幕(

splash screen
)的中心,请导航至
.../ios/Runner
。在
Assets.xcassets/LaunchImage.imageset
, 拖入图片,并命名为
images LaunchImage.png
LaunchImage@2x.png
LaunchImage@3x.png
。 如果你使用了其他的文件名,那你还必须更新同一目录中的
Contents.json
文件。

您也可以通过打开

Xcode
完全自定义
storyboard
。在
Project Navigator
中导航到
Runner/Runner
然后通过打开
Assets.xcassets
拖入图片,或者通过在
LaunchScreen.storyboard
中使用
Interface Builder
进行自定义。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: