概述

#

dart:uiColor 类的 API 正在进行更改,以支持宽色域

背景

#

Flutter 引擎已支持宽色域,并集成了 Impeller,现在正在将此支持添加到框架

Flutter 支持的 iOS 设备可以渲染更广泛的颜色,特别是在DisplayP3 色域中。在此更改后,Flutter 框架可以在 iOS Impeller 上渲染所有这些颜色,并且 Color 类已更好地准备好支持未来的色域或颜色分量位深度的更改。

变更说明

#

Color 的更改

  1. 添加了一个枚举字段,用于指定其ColorSpace
  2. 添加了使用归一化浮点数颜色分量的 API。
  3. 移除了使用可能导致数据丢失的 8 位无符号整数颜色分量的 API。

ColorSpace 的更改

  1. 添加了 displayP3 属性。

迁移指南

#

8 位无符号整数构造函数

#

Color.fromARGB 这样的构造函数保持不变并继续受支持。要利用 Display P3 颜色,您必须使用新的 Color.from 构造函数,该构造函数接受归一化的浮点数颜色分量。

dart
// Before: Constructing an sRGB color from the lower 8 bits of four integers.
final magenta = Color.fromARGB(0xff, 0xff, 0x0, 0xff);

// After: Constructing a color with normalized floating-point components.
final magenta = Color.from(alpha: 1.0, red: 1.0, green: 0.0, blue: 1.0);

Color 的实现者

#

Color 中正在添加新方法,因此任何 implements Color 的类都会中断,并且必须实现新方法,例如 Color.aColor.b

最终,实现者应迁移以利用新 API。短期内,这些方法可以轻松实现,而无需更改类的底层结构。

例如

dart
class Foo implements Color {
  int _red;

  @override
  double get r => _red / 255.0;
}

色域支持

#

使用 Color 并对颜色分量执行任何类型的计算的客户端现在应首先检查色域分量,然后再执行计算。为了做到这一点,您可以使用新的 Color.withValues 方法来执行色域转换。

迁移示例

dart
// Before
double redRatio(Color x, Color y) => x.red / y.red;

// After
double redRatio(Color x, Color y) {
  final xPrime = x.withValues(colorSpace: ColorSpace.extendedSRGB);
  final yPrime = y.withValues(colorSpace: ColorSpace.extendedSRGB);
  return xPrime.r / yPrime.r;
}

在不匹配色域的情况下对颜色分量执行计算可能导致微妙的意外结果。在前面的示例中,当使用不同色域与匹配色域计算时,redRatio 会有 0.09 的差异。

访问颜色分量

#

如果您的应用访问 Color 分量,请考虑利用浮点数分量。短期内,您可以缩放分量本身。

dart
extension IntColorComponents on Color {
  int get intAlpha => _floatToInt8(this.a);
  int get intRed => _floatToInt8(this.r);
  int get intGreen => _floatToInt8(this.g);
  int get intBlue => _floatToInt8(this.b);

  int _floatToInt8(double x) {
    return (x * 255.0).round() & 0xff;
  }
}

不透明度

#

在 Flutter 3.27 之前,Color 具有“不透明度”的概念,体现在 opacitywithOpacity() 方法中。不透明度被引入作为一种方式,通过浮点数值 ([0.0, 1.0]) 与 Color 沟通其 alpha 通道。不透明度方法是设置 8 位 alpha 值 ([0, 255]) 的便捷方法,但从未提供浮点数的完整表达。当颜色分量存储为 8 位整数时,这已经足够了。

自 Flutter 3.27 起,alpha 以浮点数值存储。使用 .a.withValues() 将提供浮点数的完整表达,并且不会被量化(限制在有限范围内)。这意味着“alpha”比“不透明度”更准确地表达了意图。不透明度在细微之处有所不同,其使用可能导致意外的数据丢失,因此 .withOpacity().opacity 已被弃用,并且其语义已得到维护,以避免破坏任何人。

例如

dart
// Prints 0.5019607843137255.
print(Colors.black.withOpacity(0.5).a);
// Prints 0.5.
print(Colors.black.withValues(alpha: 0.5).a);

实际上所有用法都将直接受益于更准确的颜色。在极少数情况下,如果不行,可以采取措施将不透明度量化到 [0, 255],使用 .alpha.withAlpha() 来匹配 Flutter 3.27 之前的行为。

迁移 opacity

#
dart
// Before: Access the alpha channel as a (converted) floating-point value.
final x = color.opacity;

// After: Access the alpha channel directly.
final x = color.a;

迁移 withOpacity

#
dart
// Before: Create a new color with the specified opacity.
final x = color.withOpacity(0.0);

// After: Create a new color with the specified alpha channel value,
// accounting for the current or specified color space.
final x = color.withValues(alpha: 0.0);

相等性

#

一旦 Color 将其颜色分量存储为浮点数,相等性的工作方式会略有不同。在计算颜色时,值可能存在微小的差异,但这些差异可以被视为相等。为了适应这一点,请使用 closeToisColorSameAs 匹配器。

dart
// Before: Check exact equality of int-based color.
expect(calculateColor(), const Color(0xffff00ff));

// After: Check rough equality of floating-point-based color.
expect(calculateColor(), isSameColorAs(const Color(0xffff00ff)));

时间线

#

第一阶段 - 新 API 介绍,旧 API 弃用

#

已发布版本:3.26.0-0.1.pre
稳定版发布:3.27.0

第二阶段 - 移除旧 API

#

生效版本:尚未发布
稳定版本:尚未发布

参考资料

#

相关议题

相关 PR