End

Flutter 陈航 16-主题定制 依赖管理 静态资源 三方库

本文地址


目录

16 | 从夜间模式说起,如何定制主题?

在 Flutter 中,通过 ThemeData 可以统一管理主题的配置信息。

  • 在 iOS 中,通过将主题的配置信息预先写到 plist 文件中,借助单例控制 App 应该使用哪种配置
  • 在 Android 中,配置信息定义在 xml 中的 style 中,通过 activity 的 setTheme 进行切换
  • 在前端中,通过简单更换 css 就可以实现多套主题/配色之间的切换

通过 ThemeData,可以实现 App 全局范围,或 Widget 局部范围的样式切换。

全局统一的视觉风格定制

在 Flutter 中,应用程序类 MaterialApp 提供了设置主题的能力,可以通过参数 theme,改变 App 的主题色、字体等。

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 主题色
        ),
      );
}

局部独立的视觉风格定制

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 全局主题
Container(
   color: Theme.of(context).primaryColor, // 使用 App 的主题色
   child: const Text('primaryColor'), 
)

根据平台选择主题

可以根据 defaultTargetPlatform 判断当前应用所运行的平台,从而来设置对应的主题。

TargetPlatform platform = defaultTargetPlatform; // package:flutter/foundation.dart
theme: platform == TargetPlatform.iOS ? kIOSTheme : kAndroidTheme, // 根据平台选择主题

17 | 依赖管理:图片、配置和字体

原文

资源管理

Flutter 中的资源(assets)可以是任意类型的文件。

Flutter 中的资源可以放在任意目录下,只需要在 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 可以根据当前设备分辨率加载最接近设备像素比例的图片资源。

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 的资源,如果有 2.0x 的资源,也需在 pubspec.yaml 中显示声明。

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 框架运行之前提前使用,这时就需要在对应的原生工程中完成相应的配置。

  • 更换 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 | 依赖管理:第三方组件库如何管理?

Flutter 中的 pubspec.yaml 与 iOS 中的 Podfile、Android 中的 build.gradle、前端的 package.json,在功能上是类似的。

除了管理资源,pubspec.yaml 更重要的作用是管理 Flutter 工程代码的依赖,比如第三方库、Dart 运行环境、Flutter SDK 版本。

Pub

与 Android 中的 Maven、iOS 中的 CocoaPods、前端中的 npm 类似,Dart 提供了官方的包仓库 Pub

在 Dart 中,库和应用都属于包

pubspec.yaml

pubspec.yaml 中定义了包的元数据、运行环境、外部依赖、资源管理等配置信息。

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 语法中的折叠换行符号,因此在指定版本范围的时候,必须使用引号

Flutter 中通常是指定版本区间,而不是直接指定特定版本,因为包升级变化很频繁,如果有其他的包直接或间接依赖这个包的其他版本时,就会经常发生冲突。

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 的另一个职责是,找出一组同时满足所有包版本约束的包版本

比较活跃的第三方包的升级通常比较频繁

对于不同数据源,Dart 会使用不同的方式进行管理,最终会将远端依赖的包全部下载到本地:

  • 对于以 版本号 方式声明的依赖,Pub 会从 pub.dartlang.org 下载包
  • 对于以 Git 仓库声明的依赖,Pub 会 clone 所指定的 Git 仓库
  • 如果依赖的包还有其他的依赖包(递归依赖),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 的图片,那么就可以通过指定 package 参数去加载依赖包中的图片。

Image.asset('assets/icon_qt.png', package: 'package_qt');
AssetImage('assets/icon_qt.png', package: 'package_qt');

案例

借助 date_format 实现格式化日期的功能。

1、首先在 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

2、把 date_format 添加到 pubspec.yaml 中:

dependencies:
  flutter:
    sdk: flutter
  date_format: ^2.0.7

3、随后,IDE 会监测到配置文件的改动,并提醒更新安装包依赖。

点击 Get dependenciesPub get 后会自动下载 date_format

4、下载完成后就可以在工程中使用了:

flog(formatDate(DateTime.now(), [mm, '月', dd, '日', hh, ':', n])); // 01月06日02:23
flog(formatDate(DateTime.now(), [m, '月第', w, '周']));             // 1月第1周

2023-1-6

posted @ 2023-01-06 02:29  白乾涛  阅读(254)  评论(0编辑  收藏  举报