Flutter Assets & Media
概述
Flutter 应用程序由代码和资源(assets)两部分组成。资源是被打包到应用程序安装包中的文件,可在运行时访问。这些资源包括静态数据文件、配置文件、图标和各种媒体文件。
支持的资源类型
Flutter 支持多种资源类型:
- 静态数据文件:JSON 配置文件、文本文件等
- 图片格式:JPEG、WebP、GIF、动画 WebP/GIF、PNG、BMP、WBMP
- 字体文件:TTF、OTF 等字体格式
- 其他媒体文件:音频、视频等
资源配置
在 pubspec.yaml 中指定资源
在项目根目录的 pubspec.yaml
文件中,通过 flutter
部分的 assets
字段来指定应用程序所需的资源:
yaml
flutter:
assets:
- assets/my_icon.png
- assets/background.png
配置规则
- 具体文件:可以指定具体的文件路径
- 整个目录 :要包含某个目录下的所有资源,在目录名称后加上
/
:
yaml
flutter:
assets:
- assets/images/
- 子目录处理:目录配置仅包含当前目录下的直接文件,不包含子目录中的文件
- 包含子目录:如需包含子目录文件,需为每个子目录单独创建条目
配置示例
yaml
flutter:
assets:
- assets/
- assets/images/
- assets/fonts/
- assets/data/config.json
资源变体(Asset Variants)
概念
Flutter 支持资源变体机制,允许为不同的设备分辨率或其他特性提供不同版本的资源文件。
目录结构示例
scss
assets/
images/
icon.png # 1.0x (基础分辨率)
2.0x/
icon.png # 2.0x 分辨率
3.0x/
icon.png # 3.0x 分辨率
配置方式
在 pubspec.yaml
中只需声明主资源:
yaml
flutter:
assets:
- assets/images/icon.png
系统会自动识别并包含所有变体文件。
自动选择机制
在运行时,Flutter 会根据设备的像素密度自动选择最合适的资源变体:
- 设备像素比为 2.0 时,加载
2.0x/icon.png
- 设备像素比为 3.0 时,加载
3.0x/icon.png
- 如果没有对应变体,则使用基础版本
资源加载
图片资源加载
Flutter 提供了多种图片加载方式,每种都适用于不同的场景:
1. 本地资源图片加载
使用 Image.asset()
最常用的本地图片加载方式:
dart
Image.asset('assets/images/logo.png')
带参数的高级用法:
dart
Image.asset(
'assets/images/logo.png',
width: 100,
height: 100,
fit: BoxFit.cover,
alignment: Alignment.center,
repeat: ImageRepeat.noRepeat,
semanticLabel: '应用程序标志',
)
使用 AssetImage
用于需要 ImageProvider 的场景:
dart
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/background.png'),
fit: BoxFit.cover,
),
),
)
2. 网络图片加载
使用 Image.network()
直接从网络 URL 加载图片:
dart
Image.network('https://example.com/image.jpg')
带参数的用法:
dart
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) {
return Icon(Icons.error);
},
)
使用 NetworkImage
用于需要 ImageProvider 的场景:
dart
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://example.com/image.jpg'),
),
),
)
3. 文件系统图片加载
使用 Image.file()
从设备文件系统加载图片:
dart
Image.file(File('/path/to/image.jpg'))
使用 FileImage
用于需要 ImageProvider 的场景:
dart
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(File('/path/to/image.jpg')),
),
),
)
4. 内存图片加载
使用 Image.memory()
从内存中的字节数据加载图片:
dart
Image.memory(uint8List)
使用 MemoryImage
用于需要 ImageProvider 的场景:
dart
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: MemoryImage(uint8List),
),
),
)
5. 高级加载方式
使用 FadeInImage 实现渐显效果
dart
FadeInImage.assetNetwork(
placeholder: 'assets/images/loading.gif',
image: 'https://example.com/image.jpg',
fadeInDuration: Duration(milliseconds: 300),
)
从网络加载,本地占位:
dart
FadeInImage.memoryNetwork(
placeholder: kTransparentImage, // 需要 import 'package:transparent_image/transparent_image.dart';
image: 'https://example.com/image.jpg',
)
使用 CachedNetworkImage(第三方包)
首先在 pubspec.yaml
中添加依赖:
yaml
dependencies:
cached_network_image: ^3.2.3
然后使用:
dart
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
fadeInDuration: Duration(milliseconds: 300),
memCacheWidth: 200,
memCacheHeight: 200,
)
图片加载方式选择指南
图片来源 | 推荐方式 | 使用场景 |
---|---|---|
应用内静态资源 | Image.asset() |
应用图标、背景图、UI 装饰图 |
网络资源 | Image.network() 或 CachedNetworkImage |
用户头像、动态内容图片 |
设备文件系统 | Image.file() |
用户选择的照片、相机拍摄的图片 |
内存字节数据 | Image.memory() |
经过处理的图片数据、生成的图片 |
性能优化建议
- 使用合适的图片格式:WebP 格式通常比 PNG/JPEG 更小
- 提供多分辨率资源:使用 1.0x、2.0x、3.0x 资源变体
- 网络图片缓存 :使用
CachedNetworkImage
避免重复下载 - 控制图片尺寸 :使用
width
、height
参数避免加载过大图片 - 懒加载:对于列表中的图片,考虑使用懒加载机制
文本资源加载
使用 rootBundle
dart
import 'package:flutter/services.dart' show rootBundle;
Future<String> loadAsset() async {
return await rootBundle.loadString('assets/data/config.json');
}
使用 DefaultAssetBundle
dart
Future<String> loadAssetWithContext(BuildContext context) async {
return await DefaultAssetBundle.of(context).loadString('assets/data/config.json');
}
二进制资源加载
dart
import 'package:flutter/services.dart' show rootBundle;
Future<ByteData> loadBinaryAsset() async {
return await rootBundle.load('assets/audio/sound.mp3');
}
字体管理
字体配置
在 pubspec.yaml
中配置自定义字体:
yaml
flutter:
fonts:
- family: Raleway
fonts:
- asset: fonts/Raleway-Regular.ttf
- asset: fonts/Raleway-Italic.ttf
style: italic
- asset: fonts/Raleway-Bold.ttf
weight: 700
- family: RobotoMono
fonts:
- asset: fonts/RobotoMono-Regular.ttf
- asset: fonts/RobotoMono-Bold.ttf
weight: 700
字体使用
dart
Text(
'Hello, Flutter!',
style: TextStyle(
fontFamily: 'Raleway',
fontSize: 18,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
),
)
字体权重配置
yaml
fonts:
- family: MyFont
fonts:
- asset: fonts/MyFont-Thin.ttf
weight: 100
- asset: fonts/MyFont-Light.ttf
weight: 300
- asset: fonts/MyFont-Regular.ttf
weight: 400
- asset: fonts/MyFont-Medium.ttf
weight: 500
- asset: fonts/MyFont-Bold.ttf
weight: 700
- asset: fonts/MyFont-Black.ttf
weight: 900
Android 原生应用图标资源文件、变量、xml 文件引用
AndroidManifest.xml 引用应用图标资源
Android/app/src/main/res/mipmap/ic_launcher/不同 dpi 的 ic_launcher.png
Android/app/src/main/res/mipmap/ic_launcher_round/不同 dpi 的 ic_launcher_round.png
xml
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round">
....
</application>
_ 注意文件夹和文件名称相同只是分别率不同 _
AndroidManifest.xml 引用变量、xml 文件
strings.xml(Android)定义字符串变量
Android/app/src/main/res/values/strings.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">kotlin</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">Android Studio</string>
<string name="nav_header_subtitle">android.studio@android.com</string>
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<string name="menu_home">Home</string>
<string name="menu_gallery">Gallery</string>
<string name="menu_slideshow">Slideshow</string>
</resources>
colors.xml(Android)定义颜色变量
Android/app/src/main/res/values/colors.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
themes.xml(Android)定义颜色变量
Android/app/src/main/res/values/themes/themes.xml
xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Kotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.Kotlin.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.Kotlin.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.Kotlin.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
AndroidManifest.xml 引用 colors.xml、strings.xml、themes.xml 变量
Android 会去 values 目录下递归所有目录下的文件夹下的 xml 文件。
xml
<application
android:label="@string/app_name"
android:theme="@style/Theme.Kotlin">
<activity
android:label="@string/app_name"
>
....
</activity>
</application>
styles.xml(Android)定义样式变量
Android/app/src/main/res/values/styles.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- 这里注意@drawable/launch_background是在drawable目录下的一个launch_background.xml文件:具体目录:
Android/app/src/main/res/drawable/launch_background.xml -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
Android 原生和 Flutter 对比总结
AndroidManifest.xml 位置
Android | Flutter | |
---|---|---|
Android/app/manifests/AndroidManifest.xml | Android/app/src/main/AndroidManifest.xml |
变量 xml 文件对比
- Android 和 Flutter 都会去 values 目录下递归所有目录下的文件夹下的 xml 文件。
Android | Flutter | |
---|---|---|
Android/app/src/main/res/values/*/ | Android/app/src/main/res/values/*/ |
- Flutter 会区分 night 文件目录,而 Android 不会。
Android | Flutter | |
---|---|---|
Android/app/src/main/res/values/themes/themes.xml、Android/app/src/main/res/values/themes/themes.xml(night) | Android/app/src/main/res/values/style.xml、Android/app/src/main/res/values-night/style.xml |
- Android 和 Flutter 的变量、文件引用方式相同
Android | Flutter | |
---|---|---|
@string/app_name、@color/primary、@style/Theme.Kotlin 、@drawable/launch_background | @string/app_name、@color/primary、@style/Theme.Kotlin 、@drawable/launch_background |
- Android 和 Flutter 的变量文件定义变量规则相同
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="external_files" path="" />
<external-cache-path name="external_cache" path="" />
</paths>
</resources>
Flutter 平台特定资源
Android 平台
应用图标配置
- 路径:
android/app/src/main/res/
- 文件:各
mipmap-*
文件夹中的ic_launcher.png
- 要求:按照 Android 开发者指南提供不同分辨率的图标
- 注意:如果您重命名了.png 文件,您还必须在 AndroidManifest.xml 的标签的 android:icon android:icon="@mipmap/ic_launcher" 属性中更新相应的名称。
所需尺寸规格:
- mipmap-ldpi/ (0.75x) - 36x36px (基准 48px)
- mipmap-mdpi/ (1.0x) - 48x48px (基准尺寸)
- mipmap-hdpi/ (1.5x) - 72x72px
- mipmap-xhdpi/ (2.0x) - 96x96px
- mipmap-xxhdpi/ (3.0x) - 144x144px
- mipmap-xxxhdpi/ (4.0x) - 192x192px
使用在线工具生成
- Android Asset Studio (官方网站)
- 网址:romannurik.github.io/AndroidAsse...
- 功能:自动生成各种分辨率的图标
- 上传一张高分辨率图片(建议 1024x1024px)
- 自动生成所有 Android 密度的图片
- MakeAppIcon
- 网址:makeappicon.com/
- 同时支持 iOS 和 Android
- 功能:自动生成各种分辨率的图标
- 建议:上传一张高分辨率图片(建议 1024x1024px)
- 自动生成所有 iOS 和 Android 密度的图片
应用 Label 配置
- 路径:
android/app/src/main/res/AndroidManifest.xml
- 应用: Label 会应用 AndroidManifest.xml 的标签的 android:label 属性中相应的名称。
- 注意:如果您重新名称,您必须在 AndroidManifest.xml 的标签的 android:label 属性中更新相应的名称。
完整示例代码
xml
<application
android:networkSecurityConfig="@xml/network_security_config"
android:name=".MyApp"
android:label="xxxxx xxxx xxxx"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
>
....
</application>
iOS 平台
应用图标配置
- 路径:
ios/Runner/Assets.xcassets/AppIcon.appiconset/
- 要求:提供 iOS 要求的各种尺寸图标文件
所需尺寸规格:
略(直接使用自动生成的图片。规格太多了不讲了)
使用在线工具生成
- MakeAppIcon
- 网址:makeappicon.com/
- 同时支持 iOS 和 Android
- 功能:自动生成各种分辨率的图标
- 建议:上传一张高分辨率图片(建议 1024x1024px)
- 自动生成所有 iOS 和 Android 密度的图片
应用 Label 配置
- 路径:
ios/Runner/Info.plist
- 应用: Label 会应用 Info.plist 的CFBundleName标签下的xxxxx xxxx xxxx 属性中相应的名称。
- 注意:如果您重新名称,您必须在 Info.plist 的CFBundleName标签下的xxxxx xxxx xxxx 属性更新相应的名称。
xml
<key>CFBundleName</key>
<string>xxxxx xxxx xxxx</string>
Flutter 更新启动图(也叫闪屏页(也称为启动页))
简单梳理:只展示图片,更高级的动画展示需要研读 Android 和 iOS 的启动图配置。
概念
在 Flutter 框架加载时,Flutter 会使用原生平台机制绘制启动页。此启动页将持续到 Flutter 渲染应用程序的第一帧。
这意味着如果你不在应用程序的 main() 方法中调用 runApp() 函数(或者更具体地说,如果你不调用 FlutterView.render() 去响应 PlatformDispatcher.onDrawFrame 的话,启动页将永远持续显示。
完成一个启动页的配置需要满足俩个条件:1.启动页图片资源,背景(定义在 drawable 目录下的不同 xml 中:例如 launch_background.xml、normal_background.xml....);2.主题一般定义在 values 目录下的 themes.xml、style.xml...文件中文件命名看个人习惯。(主题的设置主要源于移动端暗模式、亮模式)
只要为什么是 drawable 目录吗?android 默认是 drawable 目录。且他们在安卓 api 中就是这样叫的。
Android
步骤
这只是一个简单示例,用于将图片添加到白色启动页的中间。当然可绘制对象资源来实现预期效果。更高级的动画展示需要系统学习 Android 的 XML 编写。
第一步:定义启动页图片资源、背景
xml
<!-- launch_background.xml 文件 -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/bg_launch_color" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/launch_image" />
</item>
</layer-list>
<!-- normal_background.xml 文件 -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/bg_normal_color" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/normal_image" />
</item>
</layer-list>
第二步:定义主题 引用启动页图片资源、背景
在 styles.xml 或者 themes.xml 中应用不同的主题:launch_background.xml、normal_background.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="HomeTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/home_background</item>
</style>
</resources>
第三步:在 AndroidManifest.xml 中引用主题
xml
<application
// ...
>
<activity
android:name=".MyActivity"
android:theme="@style/LaunchTheme"
// ...
>
// ...
</activity>
//...
</application>
<!-- 或者 -->
<application
// ...
>
<activity
android:name=".MyActivity"
android:theme="@style/HomeTheme"
// ...
>
// ...
</activity>
//...
</application>
第四步: 普通主题在 AndroidManifest.xml 中引用
当启动页消失后,它会应用在 FlutterActivity 上。普通主题的背景仅仅展示非常短暂的时间,例如,当启动页消失后、设备方向改变或者 Activity 恢复期间。因此建议普通主题的背景颜色使用与 Flutter UI 主要背景颜色相似的纯色。
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="HomeTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/home_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources>
第五步: 普通主题在 AndroidManifest.xml 中引用
xml
<application
// ...
>
<activity
android:name=".MyActivity"
android:theme="@style/LaunchTheme"
// ...
>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
//...
</application>
<!-- 或者 -->
<application
// ...
>
<activity
android:name=".MyActivity"
android:theme="@style/HomeTheme"
// ...
>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
//...
</application>
如此一来,Android 应用程序就会在在初始化时展示对应的启动页面和普通主题。
iOS
将图片添加到启动屏幕「splash screen」的中心,请导航至 .../ios/Runner 路径。在 Assets.xcassets/LaunchImage.imageset ,拖入图片,并命名为 LaunchImage.png, LaunchImage@2x.png,LaunchImage@3x.png。如果你使用不同的文件名,那你还必须更新同一目录中的 Contents.json 文件中对应的名称。