【Flutter】Flutter3.27.0で指摘される'withOpacity' is deprecated'への対応とその背景を深ぼる
はじめに
FlutterのColorクラスには、色の透明度(アルファ値)を変更するための便利なメソッドwithOpacity
があります。このメソッドを使うと、既存の色に透明度を加えたり変更したりすることができます。
この度、2024年12月にFlutter3.27.0がリリースされました。
それに伴い今まで使用できていたwithOpacity
が非推奨になりました。
この記事ではwithOpacity
の移行方法についての解説と、
なぜ非推奨になったのかを自分なりに深掘りしていきます。
記事の対象者
-
withOpacity
の移行方法について知りたい方 - なんで
withOpacity
が非推奨になったかを知りたい方 - opacity?alpha?何それ美味しいの?っていう方
記事を執筆時点での筆者の環境
[✓] Flutter (Channel stable, 3.27.1, on macOS 15.1 24B2082 darwin-arm64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.96.2)
サンプルプロジェクト
ソースコード
タイトル
import 'package:flutter/material.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({
super.key,
});
Widget build(BuildContext context) {
const color = Colors.red;
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
spacing: 10,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const ListTile(
title: Text('普通のカラー'),
tileColor: color,
),
ListTile(
title: const Text('withOpacityは非推奨ですが、まだ使える'),
tileColor: color.withOpacity(0.5),
),
ListTile(
title: const Text('withAlphaを使った場合'),
tileColor: color.withAlpha(128),
),
ListTile(
title: const Text('公式が推奨しているwithValuesを使い、alphaに0.5を指定'),
tileColor: color.withValues(alpha: 0.5),
),
ListTile(
title: const Text('公式が推奨しているwithValuesを使い、alphaに128を指定'),
//
tileColor: color.withValues(alpha: 128),
),
],
),
),
),
);
}
}
1. 移行方法:withValues(alpha:)を使う
身も蓋もないですが、公式ですでに答えを書いてくれています。
使用するメソッドをそのままwithValues(alpha:)
に置き換えるだけです!
// 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);
サンプルコードでも正確に動作することを確認しました。
ListTile(
title: const Text('公式が推奨しているwithValuesを使い、alphaに0.5を指定'),
tileColor: color.withValues(alpha: 0.5),
),
たったこれだけなので、「とりあえず移行方法を知りたいんだ!」と言う方はここでそっと閉じてください。
あ、いいねは押してもらえると嬉しいです☺️
これ以降では個人的に気になった「なんで非推奨になったのか?」を深掘りしていきます。
ただ先に結論を述べておくと
iPhoneやMacのようなP3対応のデバイスにもちゃんと対応して、綺麗なUIを描画させるための準備のため
と考えます。
順をおって説明していきます。
2. withValuesとwithAlphaの存在
withValuesの実装を見てみると引数は他にもあります。
Color withValues(
{double? alpha,
double? red,
double? green,
double? blue,
ColorSpace? colorSpace}) {
// 一旦省略
}
しかも全部null
許容。なんなら透明度だけに限らずred
だけ変更する、または2、3こ変更することもできそうです。
ただ、Color
クラスには透明度だけを変更するメソッドwithAlpha
があります。
「なんだよ、公式はこっちを教えてくれればいいのに!」
そう思ったのですが、そのまま使ってみるとエラが出ます。
ListTile(
title: const Text('withAlphaを使った場合'),
tileColor: color.withAlpha(0.5),
),
The argument type 'double' can't be assigned to the parameter type 'int'.
要するに、int
を入れなさいと言っています。
正しくは以下です。
ListTile(
title: const Text('withAlphaを使った場合'),
tileColor: color.withAlpha(128),
),
あれ、透明度を示す値であるalphaってdouble
じゃないの?
withAlpha
の実装をみると、以下のように記述があります。
/// Returns a new color that matches this color with the alpha channel
/// replaced with `a` (which ranges from 0 to 255).
///
/// Out of range values will have unexpected effects.
Color withAlpha(int a) {
return Color.fromARGB(a, red, green, blue);
}
アルファチャンネルを
a
で置き換えた(0 から 255 の範囲)この色に一致する新しい色を返します。
推奨してきているwithValues(alpha:)
の引数はdouble
型だったのにここでは0~255のint
型を求めてきている。
どうゆうことでしょう?
これはさらに実装を深掘りしていくと理解できます。
withAlpha
の引数はint
型なのか?
3. なぜ結論
withAlpha
は内部でfromARGB
を呼び出しそこに値を渡すが、
その入力形式が ARGBに沿っているから。
そしておそらくwithAlpha
は使わない方が良い。
どうゆうことか少しづつ深掘りしていきます。
単語の説明
RGB
RGB は、デジタルで色を表現する基本的な形式の一つで、赤(Red)、緑(Green)、青(Blue)の3色の組み合わせで色を生成します。
それぞれの成分は 0~255の範囲で指定されます。透明度(Alpha)は含まれず、不透明な色を表現するのが特徴です。
ARGB
ARGB は、透明度(Alpha)を先頭に追加した形式で、Alpha、Red、Green、Blueの順に指定されます。
透明度は 0~255の整数で表され、0が完全に透明、255が完全に不透明を意味します。
この形式は低レベルのグラフィックAPIや画像処理で使用されることが多く、ピクセル単位で効率的な操作が可能です。
RGBA
RGBA は、透明度(Alpha)を末尾に追加した形式で、Red、Green、Blue、Alphaの順に指定されます。
透明度は一般的に 0.0~1.0の小数で表現され、直感的で扱いやすいため、CSSやUIフレームワークなどで広く使用されています。
Flutterでは色を入力する基本形式をARGBとしている
Flutterでは、ARGB形式が採用されており、Color.fromARGBメソッドを使って色を指定します。たとえば、Color.fromARGB(128, 255, 0, 0) は赤色(R=255, G=0, B=0)の透明度50%を表します。
結論でも述べたようにwithAlpha
はその引数のint型の値を内部でfromARGB
の引数a
に渡しています。
ではfromARGB()
はどんな実装になっているのかみてみましょう。
/// Construct an sRGB color from the lower 8 bits of four integers.
///
/// * `a` is the alpha value, with 0 being transparent and 255 being fully
/// opaque.
/// * `r` is [red], from 0 to 255.
/// * `g` is [green], from 0 to 255.
/// * `b` is [blue], from 0 to 255.
///
/// Out of range values are brought into range using modulo 255.
///
/// See also [fromRGBO], which takes the alpha value as a floating point
/// value.
const Color.fromARGB(int a, int r, int g, int b)
: this._fromARGBC(a, r, g, b, ColorSpace.sRGB);
const Color._fromARGBC(
int alpha, int red, int green, int blue, ColorSpace colorSpace)
: this._fromRGBOC(
red, green, blue, (alpha & 0xff) / 255, colorSpace);
/// Construct an sRGB color from the lower 8 bits of four integers.
///
/// *a
is the alpha value, with 0 being transparent and 255 being fully
[和訳]
4つの整数の下位8ビットからsRGBカラーを構築します。
a
はアルファ値で、0は透明、255は完全に表示される
つまり、fromARGB
はARGBで入れた値から指定されたColorSpace
に変換する_fromARGBC
を呼び出しています。
ColorSpace
については後述しますが、ひとまずこの段階ではまだalphaの値は整数です。
次にその中身をみると_fromRGBOC
を呼び出すのですが、ここに引数alphaとして渡された値を以下のように計算して渡しています。
(alpha & 0xff) / 255
この式は、「整数の透明度(0~255)を小数(0.0~1.0)に変換し、安全な値範囲に制限する」処理です。
alpha & 0xff
- & はビット単位のAND演算子です。
- 0xff は16進数で「11111111」(8ビットの全ビットが1)を意味し、値としては 10進数の255 です。
- この処理は、alpha の最下位8ビット(1バイト分)だけを取り出します。
- alpha が仮に 0~255 を超える範囲(例えば256や-1など)だった場合、最下位8ビットだけを有効な値として取り出すことで、範囲外の値を切り捨てます。
- これにより、alpha が意図しない範囲の値になっても、透明度の計算が安全に行えます。
/ 255
- 取り出した8ビットの値(0~255)を 255で割る ことで、透明度を 小数(0.0~1.0) に正規化します。
そしてさらに中身を追うと_fromRGBOC
はこうなっています。
/// Create an sRGB color from red, green, blue, and opacity, similar to
/// `rgba()` in CSS.
///
/// * `r` is [red], from 0 to 255.
/// * `g` is [green], from 0 to 255.
/// * `b` is [blue], from 0 to 255.
/// * `opacity` is alpha channel of this color as a double, with 0.0 being
/// transparent and 1.0 being fully opaque.
///
/// Out of range values are brought into range using modulo 255.
///
/// See also [fromARGB], which takes the opacity as an integer value.
const Color.fromRGBO(int r, int g, int b, double opacity)
: this._fromRGBOC(r, g, b, opacity, ColorSpace.sRGB);
const Color._fromRGBOC(int r, int g, int b, double opacity, this.colorSpace)
: a = opacity,
r = (r & 0xff) / 255,
g = (g & 0xff) / 255,
b = (b & 0xff) / 255;
/// Create an sRGB color from red, green, blue, and opacity, similar to
///rgba()
in CSS.
[和訳]
赤、緑、青、不透明度からsRGBカラーを作成します。
CSSのrgba()
と同様です。
そのままですが、CSSと同様のRGBAの引数としてalphaもdouble
にして渡しているのです。
fromRGBOC
はfrom red, green, blue, and opacityということですね。
ここでは最終的に色成分であるr,g,bも小数点表示に変換しています。
つまり最終的にalphaの値は浮動小数点型であるdouble
に変換して渡されるのです。
fromARGB
をfrom
に置き換えるように推奨している
公式でここまで説明してきたfromARGB
メソッドは前述の通りARGB形式での入力でした。
しかし、公式ではこのメソッドを以下のようなfrom
メソッドに置き換えるように推奨しています。
// 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);
Constructors like Color.fromARGB remain unchanged and have continued support. To take advantage of Display P3 colors, you must use the new Color.from constructor that takes normalized floating-point color components.
[和訳]
Color.fromARGBのようなコンストラクタは変更されず、引き続きサポートされています。
ディスプレイP3の色を利用するには、正規化された浮動小数点カラーコンポーネントを使用する新しいColor.fromコンストラクタを使用する必要があります
この公式の発表から推測するに、ディスプレイP3に対応するにあたって色に関わる数値に関しては浮動小数点で入力するように、移行してほしいという意思が伝わってきます。
まとめ
withAlpha
はなんらかの理由でたまたま残されているAPIの可能性が高いです。
今後はwithValues
を使うように推奨していることからいずれ非推奨なAPIになる可能性が高いので使わない方が良いと考えます。
同じ理由でその他の単体で変更するメソッドである、withRed
,withGreen
,withBlue
も使用を控えた方が良さそうです。
ColorSpace
の導入による影響
4. DisplayP3対応準備に向けた
ColorSpace
概要
Flutter3.27.0では新たにColorSpace
という列挙型を導入したと発表がありました。
Flutter ColorSpace Documentation
この列挙型には色空間として以下の3つの種類が定義されています。
enum ColorSpace {
sRGB,
extendedSRGB,
displayP3,
}
色空間とは、コンピュータやディスプレイ、印刷物などで色を表現するための基準やモデルです。
色の成分(例: 赤、緑、青など)を数値で表し、それらを特定の規則で組み合わせて色を再現します。
その基準モデルの中からFlutterでは以下の3タイプを定義したと言っています。
sRGB
デジタル機器で標準的に使用される色空間で、赤(Red)、緑(Green)、青(Blue)の成分を0~255の範囲で指定し、色を表現します。
人間の視覚に基づいて設計され、Web、ディスプレイ、プリンターなど幅広い用途で採用されています。
普及率が非常に高く、非対応デバイスはほとんど存在しないため、一貫性のある色再現が可能です。
extendedSRGB
Flutter独自の方式?
sRGBと後方互換性があり、その範囲外の色を[0..1]以外の値で表現できる色空間。
拡張された値を表示するには、[ImageByteFormat.rawExtendedRgba128]のような[ImageByteFormat]を使用する必要があります。
Display P3
sRGBより広い色域を持つ広色域(Wide Gamut)色空間で、特に赤と緑の色表現が豊かです。Appleが推進しており、iPhoneやMacなどのデバイスで広く採用されています。DCI-P3をベースにディスプレイ用に最適化されており、非対応デバイスでは自動的にsRGBにクランプされます。写真や映像制作など、鮮やかな色再現が求められる場面で使われます。
このissueがことの発端のようです。
和訳
まず、私のアプリは画像でいっぱいです。
iPhone(iPhone 7以降)、iPad、Mac、Androidデバイスは、数年前からDisplay P3ワイドファイマンを使用しています。
携帯電話が25%広い色空間を提供するより広いP3色域をサポートしているときに、ユーザーに古いsRGB色を提供するのは前世紀のようなものです。
特に画像にとって重要です。iOSとAndroidの色の不一致
第二に、同じ問題に関連しているかどうかはわかりませんが、同じFlutterコードベースから実行する場合、AndroidとiOSの色に不一致があります。
詳細はこちら:#39113
フラッターはネイティブコードと同じ品質を達成する必要があります第三に、ネイティブ iOS アプリを作成する場合、アプリを書き込むカラー プロファイル (sRGB/P3) を選択できます。
したがって、フラッターアプリ(ネイティブアプリではなく)を作成することを選択した場合、制限され、同じ結果が得られません。
ネイティブと同じフラッターで達成したい!
提案
Flutterアプリにどの色のプロファイルを書きたいかを選択するオプションを追加してください。そうすれば、それらは意図したとおりに本当に美しいです。それはフラッターの柱の1つですよね?
withValue
にはColorSpace
に対応する処理がすでに定義されている
/// Returns a new color that matches this color with the passed in components
/// changed.
///
/// Changes to color components will be applied before applying changes to the
/// color space.
Color withValues(
{double? alpha,
double? red,
double? green,
double? blue,
ColorSpace? colorSpace}) {
Color? updatedComponents;
if (alpha != null || red != null || green != null || blue != null) {
updatedComponents = Color.from(
alpha: alpha ?? a,
red: red ?? r,
green: green ?? g,
blue: blue ?? b,
colorSpace: this.colorSpace);
}
if (colorSpace != null && colorSpace != this.colorSpace) {
final _ColorTransform transform =
_getColorTransform(this.colorSpace, colorSpace);
return transform.transform(updatedComponents ?? this, colorSpace);
} else {
return updatedComponents ?? this;
}
}
/// Construct a color with normalized color components.
///
/// Normalized color components allows arbitrary bit depths for color
/// components to be be supported. The values will be normalized relative to
/// the [ColorSpace] argument.
const Color.from(
{required double alpha,
required double red,
required double green,
required double blue,
this.colorSpace = ColorSpace.sRGB})
: a = alpha,
r = red,
g = green,
b = blue;
これをみる限りだと、withValues
の内部で呼ばれるColor.from
には現状何も値を入れなければ問答無用でColorSpace.sRGB
が入るようになっています。
ではwithValues
の引数にColorSpace.displayP3
を渡した場合はどうなるのか?
その場合はwithValues
のif文の中で書かれている_getColorTransform(this.colorSpace, colorSpace);
によって現在のColorSpace
と引数で指定された変更すべきColorSpace
に対応した変換ロジックを取得しています。
_getColorTransform
_ColorTransform _getColorTransform(ColorSpace source, ColorSpace destination) {
// The transforms were calculated with the following octave script from known
// conversions. These transforms have a white point that matches Apple's.
//
// p3Colors = [
// 1, 0, 0, 0.25;
// 0, 1, 0, 0.5;
// 0, 0, 1, 0.75;
// 1, 1, 1, 1;
// ];
// srgbColors = [
// 1.0930908918380737, -0.5116420984268188, -0.0003518527664709836, 0.12397786229848862;
// -0.22684034705162048, 1.0182716846466064, 0.00027732315356843174, 0.5073589086532593;
// -0.15007957816123962, -0.31062406301498413, 1.0420056581497192, 0.771118700504303;
// 1, 1, 1, 1;
// ];
//
// format long
// p3ToSrgb = srgbColors * inv(p3Colors)
// srgbToP3 = inv(p3ToSrgb)
const _MatrixColorTransform srgbToP3 = _MatrixColorTransform(<double>[
0.808052267214446, 0.220292047628890, -0.139648846160100,
0.145738111193222, //
0.096480880462996, 0.916386732581291, -0.086093928394828,
0.089490172325882, //
-0.127099563510240, -0.068983484963878, 0.735426667591299, 0.233655661600230
]);
const _ColorTransform p3ToSrgb = _MatrixColorTransform(<double>[
1.306671048092539, -0.298061942172353, 0.213228303487995,
-0.213580156254466, //
-0.117390025596251, 1.127722006101976, 0.109727644608938,
-0.109450321455370, //
0.214813187718391, 0.054268702864647, 1.406898424029350, -0.364892765879631
]);
switch (source) {
case ColorSpace.sRGB:
switch (destination) {
case ColorSpace.sRGB:
return const _IdentityColorTransform();
case ColorSpace.extendedSRGB:
return const _IdentityColorTransform();
case ColorSpace.displayP3:
return srgbToP3;
}
case ColorSpace.extendedSRGB:
switch (destination) {
case ColorSpace.sRGB:
return const _ClampTransform(_IdentityColorTransform());
case ColorSpace.extendedSRGB:
return const _IdentityColorTransform();
case ColorSpace.displayP3:
return const _ClampTransform(srgbToP3);
}
case ColorSpace.displayP3:
switch (destination) {
case ColorSpace.sRGB:
return const _ClampTransform(p3ToSrgb);
case ColorSpace.extendedSRGB:
return p3ToSrgb;
case ColorSpace.displayP3:
return const _IdentityColorTransform();
}
}
}
そこからさらにtransform.transform(updatedComponents ?? this, colorSpace);
では変数の
transform
で取得したロジック(クラス)に該当する変換メソッドであるtransform
を使ってColor
型に変換して成果物を返却しています。
変数の`transform`はこの中のどれかのクラスに当てはまり、そのクラスの中の`transform`メソッドを実行している
class _IdentityColorTransform implements _ColorTransform {
const _IdentityColorTransform();
Color transform(Color color, ColorSpace resultColorSpace) => color;
}
class _ClampTransform implements _ColorTransform {
const _ClampTransform(this.child);
final _ColorTransform child;
Color transform(Color color, ColorSpace resultColorSpace) {
return Color.from(
alpha: clampDouble(color.a, 0, 1),
red: clampDouble(color.r, 0, 1),
green: clampDouble(color.g, 0, 1),
blue: clampDouble(color.b, 0, 1),
colorSpace: resultColorSpace);
}
}
class _MatrixColorTransform implements _ColorTransform {
/// Row-major.
const _MatrixColorTransform(this.values);
final List<double> values;
Color transform(Color color, ColorSpace resultColorSpace) {
return Color.from(
alpha: color.a,
red: values[0] * color.r +
values[1] * color.g +
values[2] * color.b +
values[3],
green: values[4] * color.r +
values[5] * color.g +
values[6] * color.b +
values[7],
blue: values[8] * color.r +
values[9] * color.g +
values[10] * color.b +
values[11],
colorSpace: resultColorSpace);
}
}
まとめ
Flutter公式が言うようにiPhoneやMacのようなP3にちゃんと対応して綺麗なUIを描画したいのならwithValues
を使いましょう。
現状はまだ移行期間のため、withValues
の恩恵は受けづらいです。
しかし、ほぼほぼこの実装に切り替えてくる予感がします。
終わりに
Flutter 3.27.0のリリースに伴い、withOpacityが非推奨となり、新たにwithValuesを使用することが推奨されています。
今回の記事では、その移行方法だけでなく、背景にある理由や色空間への対応について深掘りしました。
現状ではwithValuesを使用することで、sRGB基準での描画を維持しながら透明度や色成分を柔軟に操作できます。
そして、将来的にはDisplayP3などの広色域対応デバイスでの色再現も視野に入れた設計となっていることがわかりました。
Flutterの進化により、より多様なデバイスや色空間に対応できるようになる期待が高まります。
この移行期間中にwithValuesへの対応を進めることで、将来の変更にもスムーズに適応できるでしょう。
この記事がその一助になれば幸いです。
最後までお読みいただきありがとうございました!質問やご意見があれば、ぜひコメントで教えてください🙇♂️
Discussion