Flutterで画像にカラーフィルタをかける方法
Flutterでダークモードに対応するために、画像を少し暗くする方法を調査しました。
ダークモードに対応する画像
ダークモード設定時、テキスト、Buton、Iconなどは自動でダークテーマに対応したカラーに変わります。
しかし、自前で用意した画像は自動では切り替わりません。
少し暗めの画像を別途用意して切り替えてもいいのですが、ざっくりと画像を暗くして対応する方法も考えられます。
Flutterには、画像やWidgetにカラーフィルターを掛ける方法がいくつかあるようです。
バグもありましたので最後のほうに載せておきます。
Flutterで画像にカラーフィルタをかける方法
ここでは画像を少し暗くする方法として記載しています。
ImageでcolorBlendを使用する
Image.asset(
'assets/docs/pic1.png',
color: Colors.black.withOpacity(0.2),
colorBlendMode: BlendMode.darken,
),
ImageとContainerをStackで重ねる
Containerにサイズを指定するのがちょっと手間
画像のサイズに合わせます。
Stack(
children: <Widget>[
Image.asset(
'images/pic1.png',
height: 100.0,
),
Container(
height: 100.0,
width: 200.0,
color: Colors.black.withOpacity(0.4),
),
],
),
ColorFilteredのColorFilter.modeを使用する
SingleChildScrollViewの中で使用すると、そのページを表示するとき、スクロール開始時にフィルタが全体に回ってしまうバグがある。ListViewの中ではそんなに発生しないようだ。
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.4),
BlendMode.darken,
),
child: Image.asset('images/300x300.png'),
),
ColorFilteredのColorFilter.matrixを使用する
final ColorFilter darken50 = const ColorFilter.matrix(<double>[
0.5, 0, 0, 0, 0, // Red
0, 0.5, 0, 0, 0, // Green
0, 0, 0.5, 0, 0, // Blue
0, 0, 0, 1, 0, // Alpha
]);
ColorFiltered(
colorFilter: darken80,
child: Image.asset('images/pic1.png'),
),
Containerの背景画像にColorFilter.modeを使用する
他の手法より手間がかかる。
ページの背景画像でよくみるコードかも。
Container(
width: 200.0,
height: 100.0,
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: ColorFilter.mode(
// 暗くする
Colors.black.withOpacity(0.2),
BlendMode.darken,
),
image: const AssetImage('images/pic1.png'),
fit: BoxFit.cover,
),
),
// なにか手前に表示する
child: const Center(
child: Text(
'手前',
style: TextStyle(fontSize: 24, color: Colors.white),
),
),
),
ShaderMask
Shadermaskで、LinearGradientを使ってもできますよと、Chat-GPTが教えてくれた。
ShaderMask(
shaderCallback: (Rect bounds) {
return LinearGradient(
colors: [
Colors.black.withOpacity(0.5),
Colors.black.withOpacity(0.5),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
).createShader(bounds);
},
blendMode: BlendMode.darken,
child: Image.asset('images/pic1.png', height: pictureHeight),
),
Opacityで透過させる
ダークテーマ時は背景も暗いだろうから、画像を透過させれば暗くなるはずという作戦。
これは暗いフィルターをかける処理ではないが同じような結果を生む。
Opacity(
opacity: 0.5,
child: Image.asset('images/pic1.png', height: 100.0),
),
Imageで透過させる
Imageでも透過させて表示させることは可能。
BlendModeは、dstAtopを使ってみた。
Image.asset(
'images/pic1.png',
height: pictureHeight,
color: Colors.transparent.withOpacity(0.5),
colorBlendMode: BlendMode.dstATop, // modeによる
),
ColorFilteredのバグ
SingleChildScrollViewの中で使用するとフィルタが全体に回ってしまう様な現象が発生することがあります。
発生タイミングは、ページ表示、画面をスクロールしない方向に引っ張ってからスクロールするときの開始時です。
ListViewでも発生しますがSingleChildScrollViewほど発生しませんでした。
ダークモード時の対応
ちなみに、ダークモード時の対応コード。私はこんな感じ。
// contextが必要なのでbuild内に記述しています
// モードによって透過率が変わります
final isDark = Theme.of(context).brightness == Brightness.dark;
final opacity = isDark ? 0.2 : 0.0;
// ダークモードの時は少し暗くなります
Image.asset(
'assets/docs/pic1.png',
color: Colors.black.withOpacity(opacity),
colorBlendMode: BlendMode.darken,
),
調査環境
- Windows11
- Flutter version 3.24.2
- Pixel 8 API 34 emulator
- Pixel 6a 実機
関連記事
参照
Image class – Flutter
ColorFiltered class – Flutter
ShaderMask class – Flutter
Opacity class – Flutter