目录
目录16 | 从夜间模式说起,如何定制主题?
在 iOS 中,我们通常会将主题的配置信息预先写到 plist
文件中,通过一个单例来控制 App 应该使用哪种配置;而 Android 的配置信息则写入各个 style
属性值的 xml 中,通过 activity 的 setTheme
进行切换;前端的处理方式也类似,简单更换 css 就可以实现多套主题/配色之间的切换。
Flutter 也提供了类似的能力,由 ThemeData 来统一管理主题的配置信息。
通过 ThemeData,可以实现 App 全局范围,或是 Widget 局部范围的样式切换。
全局统一的视觉风格定制
在 Flutter 中,应用程序类 MaterialApp 的初始化方法,为我们提供了设置主题的能力。我们可以通过参数 theme
,选择改变 App 的主题色、字体等,设置界面在 MaterialApp 下的展示样式。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
home: const HomePage(title: '白乾涛'),
theme: ThemeData(
brightness: Brightness.dark, // 明暗模式
primaryColor: Colors.green, // 应用主色调
iconTheme: const IconThemeData(color: Colors.red), // icon 主题色
),
);
}
局部独立的视觉风格定制
在 Flutter 中可以使用 Theme 来对 App 的主题进行局部覆盖。Theme 是一个单子 Widget 容器,可以通过设置其 data 属性,对其子 Widget 进行样式定制:
- 如果我们不想继承任何 App 全局的颜色或字体样式,可以直接新建一个 ThemeData 实例
- 如果我们想继承 App 的主题,并只更新部分样式,可以使用
copyWith
方法
class _HomePageState extends State<HomePage> {
double size = 30;
@override
Widget build(BuildContext context) {
ThemeData theme1 = ThemeData(iconTheme: const IconThemeData(color: Colors.blue));
ThemeData theme2 = Theme.of(context).copyWith(iconTheme: const IconThemeData(color: Colors.green));
Icon icon1 = Icon(Icons.thumb_up, size: size);
Icon icon2 = Icon(Icons.favorite, size: size);
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Column(
children: [
Row(children: [Theme(data: theme1, child: icon1), Theme(data: theme1, child: icon2)]),
Row(children: [Theme(data: theme2, child: icon1), Theme(data: theme2, child: icon2)]),
Row(children: [icon1, icon2]),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => size += 10),
child: Text(size.toInt().toString()),
));
}
}
获取样式特定的值
我们可以通过 Theme.of(context).xxx
取出样式某一属性的值。
Theme.of(context)
将向上查找 Widget 树,并返回 Widget 树中最近的主题 Theme。如果 Widget 的父 Widget 们有一个单独的主题定义,则使用该主题。如果都没有,那就使用 App 全局主题。
在下面的例子中,我们创建了一个包装了一个 Text 组件的 Container 容器。在 Text 组件的样式定义中,我们复用了全局的 title 样式,而在 Container 的背景色定义中,则复用了 App 的主题色:
Container(
color: Theme.of(context).primaryColor, // 容器背景色复用应用主题色
child: const Text('primaryColor'),
)
根据平台选择主题
我们可以根据 defaultTargetPlatform
来判断当前应用所运行的平台,从而根据系统类型来设置对应的主题。
TargetPlatform platform = defaultTargetPlatform; // package:flutter/foundation.dart
theme: platform == TargetPlatform.iOS ? kIOSTheme : kAndroidTheme, // 根据平台选择主题
17 | 依赖管理:图片、配置和字体
资源管理
在 Flutter 中,资源(assets)可以是任意类型的文件。
Flutter 并没有像 Android 那样预先定义资源的目录结构,我们可以把资源存放在项目中的任意目录下,只需要在根目录下的 pubspec.yaml
文件中,对这些资源的所在位置进行显式声明就可以了。
声明时,我们既可以对每一个文件进行挨个声明,也可以采用子目录批量声明的方式。
assets
├── background.jpg
├── icons
│ ├── food1.jpg
│ └── food2.jpg
├── loading.gif
└── result.json
对于上述资源文件存放的目录结构,以下代码分别演示了挨个指定和子目录批量指定这两种方式:通过单个文件声明的,我们需要完整展开资源的相对路径;而对于目录批量指定的方式,只需要在目录名后加路径分隔符就可以了:
flutter:
assets:
- assets/background.jpg # 挨个指定资源路径
- assets/loading.gif # 挨个指定资源路径
- assets/result.json # 挨个指定资源路径
- assets/icons/ # 子目录批量指定
- assets/ # 根目录也可以批量指定
注意:目录批量指定并不递归,只有在该目录下的文件才可以被包括,如果下面还有子目录的话,需要单独声明子目录下的文件。
完成资源的声明后,我们就可以在代码中访问它们了。
- 图片资源:通过
Image.asset
访问 - 字符串文件:通过
rootBundle.loadString
访问 - 二进制文件:通过
rootBundle.load
访问
class _HomePageState extends State<HomePage> {
String _text = "1";
void _onPressed() => rootBundle.loadString('assets/result.json').then((msg) {
_text = msg;
setState(() => flog(_text));
});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Column(children: [Text(_text), Image.asset("assets/images/monkey.jpg")]),
floatingActionButton: FloatingActionButton(
onPressed: _onPressed,
child: Text(_text),
));
}
基于像素密度的图片
How to Find Device Metrics for Any Screen
与 Android、iOS 开发类似,Flutter 也遵循了基于像素密度的管理方式,如 1.0x、2.0x、3.0x 或其他任意倍数,Flutter 可以根据当前设备分辨率加载最接近设备像素比例的图片资源。
为了让 Flutter 更好地识别,我们的资源目录应该将 1.0x、2.0x 与 3.0x 的图片资源分开管理。
assets
├── background.jpg // 1.0x 图
├── 2.0x
│ └── background.jpg // 2.0x 图
└── 3.0x
└── background.jpg // 3.0x 图
而在 pubspec.yaml
文件声明这个图片资源时,仅声明 1.0x 图资源即可:
flutter:
assets:
- assets/background.jpg # 1.0x 图资源
注意:1.0x 分辨率的图片是资源标识符,即使没有包含 1.0x 资源,仍需在 pubspec.yaml 中将它显示声明出来。
Flutter 会根据实际屏幕像素比例加载相应分辨率的图片,如果主资源缺少某个分辨率资源,Flutter 会在剩余的分辨率资源中选择最接近的分辨率资源去加载。
字体
在 Flutter 中,使用自定义字体同样需要在 pubspec.yaml
文件中提前声明。
fonts:
- family: RobotoCondensed # 字体名
fonts:
- asset: assets/fonts/RobotoCondensed-Regular.ttf # 普通字体
- asset: assets/fonts/RobotoCondensed-Italic.ttf
style: italic # 斜体
- asset: assets/fonts/RobotoCondensed-Bold.ttf
weight: 700 # 粗体
这些声明都对应着 TextStyle 中的样式属性:
- 字体名 family 对应 fontFamily 属性
- 字体样式 style 对应 style 属性
- 斜体 italic
- 正常体 normal
- 字体粗细 weight 对应 fontWeight 属性
在使用时,我们只需要在 TextStyle 中指定对应的字体即可:
Text("普通字体", style: TextStyle(fontFamily: 'RobotoCondensed'));
Text("粗体", style: TextStyle(fontFamily: 'RobotoCondensed', fontWeight: FontWeight.w700));
Text("斜体", style: TextStyle(fontFamily: 'RobotoCondensed', fontStyle: FontStyle.italic));
原生平台的资源
Flutter 需要原生环境才能运行,但是有些资源我们需要在 Flutter 框架运行之前提前使用,这时就需要在对应的原生工程中完成相应的配置。
- 更换 App 启动图标
对于 Android 平台,启动图标位于根目录 android/app/src/main/res/mipmap
下。我们只需要遵守对应的像素密度标准,保留原始图标名称,将图标更换为目标资源即可:
- 更换启动图
对于 Android 平台,启动图位于根目录 android/app/src/main/res/drawable
下,是一个名为 launch_background
的 XML 文件。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 白色背景 -->
<item android:drawable="@android:color/white" />
<item>
<!-- 内嵌一张居中展示的图片 -->
<bitmap
android:gravity="center"
android:src="@mipmap/bitmap_launcher" />
</item>
</layer-list>
18 | 依赖管理:第三方组件库如何管理?
pubspec.yaml
与 iOS 中的 Podfile、Android 中的 build.gradle
、前端的 package.json
在功能上是类似的。
除了管理资源,其更重要的作用是管理 Flutter 工程代码的依赖,比如第三方库、Dart 运行环境、Flutter SDK 版本。
Pub
与 Android 中的 JCenter/Maven、iOS 中的 CocoaPods、前端中的 npm 库类似,Dart 提供了官方的包仓库 Pub。通过 Pub,我们可以很方便地查找到有用的第三方包。
在 Dart 中,库和应用都属于包
pubspec.yaml
pubspec.yaml
是包的配置文件,包含了包的元数据(包的名称和版本)、运行环境(Dart SDK 与 Fluter SDK 版本)、外部依赖、内部配置(资源管理)。
name: flutter_app_demo # 应用名称
description: 应用描述. # 应用描述
version: 1.0.0 # 应用版本
environment: # Dart 运行环境区间
sdk: ">=2.1.0 <3.0.0" # 支持 2.1 至 3.0 之间的 Dart 版本
dependencies: # 依赖库
flutter:
sdk: flutter
cupertino_icons: ">0.1.1" # 版本约束信息,引用任意大于 0.1.1 的版本
版本约束方式
版本约束信息支持指定版本、版本号区间、任意版本这三种版本约束方式。
注意:
- 由于元数据与名称使用空格分隔,因此版本号中
不能出现空格
- 由于大于符号
>
是 YAML 语法中的折叠换行符号,因此在指定版本范围的时候,必须使用引号
。
对于包,我们通常是指定版本区间,而很少直接指定特定版本,因为包升级变化很频繁,如果有其他的包直接或间接依赖这个包的其他版本时,就会经常发生冲突。
environment
建议将 Dart 与 Flutter 的 SDK 环境写死,统一团队的开发环境
environment:
sdk: 2.3.0
flutter: 1.2.1
数据源
只有在 Pub 上进行公开发布过的包,才可以以版本的方式引用。否则,我们需要设置数据源,使用本地路径或 Git 地址的方式进行包声明。
dependencies:
package1:
path: ../package1/ # 路径依赖
date_format:
git:
url: https://github.com/xxx/package2.git # git 依赖
pubspec.lock
在开发应用时,我们可以不写明具体的版本号,而是以区间的方式声明包的依赖;但对于一个程序而言,其运行时具体引用哪个版本的依赖包必须要确定下来。因此,包管理工具 Pub 的另一个职责是,找出一组同时满足每个包版本约束的包版本。包版本一旦确定,接下来就是下载对应版本的包了。
对于 dependencies 中的不同数据源,Dart 会使用不同的方式进行管理,最终会将远端依赖的包全部下载到本地。
- 对于 Git 声明依赖的方式,Pub 会
clone
Git 仓库 - 对于版本号的方式,Pub 则会从
pub.dartlang.org
下载包 - 如果包还有其他的依赖包,Pub 也会一并下载
在完成了所有依赖包的下载后,Pub 会在应用的根目录下创建 .packages
文件,将依赖的包名与系统缓存中的包文件路径进行映射,方便后续维护。
最后,Pub 会自动创建 pubspec.lock 文件,其作用类似 iOS 的 Podfile.lock 或前端的 package-lock.json
文件,用于记录当前状态下实际安装的各个直接依赖、间接依赖的包的具体来源和版本号。
比较活跃的第三方包的升级通常比较频繁,因此我们需要把
pubspec.lock
文件也一并提交到代码版本管理中,这样团队中的所有人在使用这个应用时安装的所有依赖都是完全一样的。
现代编程语言大都自带第依赖管理机制,其核心功能是为工程中所有直接或间接依赖的代码库找到合适的版本,但这并不容易。
就比如前端的依赖管理器 npm 的早期版本,就曾因为不太合理的算法设计,导致计算依赖耗时过长,依赖文件夹也高速膨胀,一度被开发者们戏称为“黑洞”。
而 Dart 使用的 Pub 依赖管理机制所采用的 PubGrub 算法则解决了这些问题,因此被称为下一代版本依赖解决算法,在 2018 年底被苹果公司吸纳,成为 Swift 所采用的依赖管理器算法。
当然,如果你的工程里的依赖比较多,并且依赖关系比较复杂,即使再优秀的依赖解决算法也需要花费较长的时间才能计算出合适的依赖库版本。如果我们想减少依赖管理器为你寻找代码库依赖版本所耗费的时间,一个简单的做法就是从源头抓起,在 pubspec.yaml 文件中固定那些依赖关系复杂的第三方库们,及它们递归依赖的第三方库的版本号。
依赖包的资源
除了提供功能和代码维度的依赖之外,包还可以提供资源的依赖。
例如,我们的应用依赖了一个名为 package_qt
的包,其包含一个 icon_qt.png
的图片,那么,在我们应用中,就可以通过 Image 和 AssetImage 提供的 package
参数去加载依赖包中的图片。
Image.asset('assets/icon_qt.png', package: 'package_qt');
AssetImage('assets/icon_qt.png', package: 'package_qt');
案例
date_format 提供了若干常用的日期格式化方法,可以很方便地实现格式化日期的功能。
首先,我们在 Pub 上找到 date_format 这个包:
date_format: ^2.0.7
A simple API to format dates.
Use formatDate function to format a DateTime object.
print(formatDate(DateTime(1989, 02, 21), [yyyy, '-', mm, '-', dd]));
1989-02-21
把 date_format 添加到 pubspec.yaml 中:
dependencies:
flutter:
sdk: flutter
date_format: ^2.0.7
随后,IDE 监测到了配置文件的改动,提醒我们进行安装包依赖更新。于是,我们点击 Get dependencies
或 Pub get
,下载 date_format。
下载完成后就可以在工程中使用了:
flog(formatDate(DateTime.now(), [mm, '月', dd, '日', hh, ':', n])); // 01月06日02:23
flog(formatDate(DateTime.now(), [m, '月第', w, '周'])); // 1月第1周
2023-1-6
标签:依赖,assets,16,Pub,陈航,版本,Flutter,资源 From: https://www.cnblogs.com/baiqiantao/p/17029308.html