0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FlutterでSVGの色を動的に変更したい

Last updated at Posted at 2024-11-28

FlutterでSVGの色を動的に変更する実装方法

はじめに

SVGファイルは拡大縮小しても品質が劣化しない優れたベクター画像形式です。アプリ内で色を動的に変更したい場合、単純な画像置き換えでは対応できません。

今回は、SVGファイル内の特定の要素の色を自由に変更できるウィジェットの実装方法をご紹介します。

SVG の色を動的に変更するとできること

百聞は一見に如かず。という事で下記をご覧ください。

change_svg_color.gif

私が現在開発中のアプリではテーマカラー選択ができるのですが、
ワンちゃんの画像のスカーフの部分の色だけが
テーマカラーに合わせて変わっていると思います。
こんなふうに、動的に色を変えられるようになるのがメリットです。

SVG ファイルの準備

まず、動的に色を変更させるsvgファイルを用意します。
svg形式で取得可能なフリー素材を用いればOKですが、迷ったらこのサイトから探してみてください。

unDraw

色を変更したい要素に id= で適当な文字列を渡します。
例では id="change_color_target" を追加しています。

<svg xmlns="http://www.w3.org/2000/svg" ...>
  <!-- 色を変更したい要素にIDを追加 -->
  <path id="change_color_target" 
        d="..." 
        fill="#000000"/>
  <!-- 他の要素 -->
  <path d="..." fill="#3f3d56"/>
</svg>

上記例では視認性向上のために改行しているが、
実際のSVGファイルでは以下のように1行で記述すること:

<svg xmlns="http://www.w3.org/2000/svg" ...><path id="change_color_target" d="..." fill="#000000"/><path d="..." fill="#3f3d56"/></svg>

改行を避けるべき理由

XMLパーサーの挙動
FlutterのSVGパーサーは、XMLの構文解析を厳密に行います
改行やインデントが入ることで、予期せぬ場所で要素が分割される可能性があります
これにより XmlParserException: ">" expected at XX:XX のようなエラーが発生することがあります

pubspec.yaml の準備

まず、必要なパッケージをpubspec.yamlに追加します:

dependencies:
  flutter_svg: ^2.0.14

次に、SVGファイルをアセットとして登録します:

flutter:
  assets:
    - assets/images/example.svg

SVGファイルはassets/imagesディレクトリに配置することを推奨します。異なるディレクトリに配置する場合は、パスを適切に変更してください。

パッケージのインストールとアセットの登録が完了したら、以下のコマンドを実行します:

flutter pub get

実装

ColorChangeSvgクラスを作成します:

このファイルは特に変更不要

import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';

/// SVGファイル内の特定の要素の色を変更するウィジェット
class ColorChangeSvg extends StatelessWidget {
  final String assetName;
  final Map<String, Color> colorMapping;
  final double width;
  final double height;

  const ColorChangeSvg({
    required this.assetName,
    required this.colorMapping,
    required this.width,
    required this.height,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: DefaultAssetBundle.of(context).loadString(assetName),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SizedBox(width: width, height: height);
        }

        String svgContent = snapshot.data!;

        // 各ターゲットに対して色を変更
        colorMapping.forEach((targetId, color) {
          final hexColor = '#${color.value.toRadixString(16).padLeft(8, '0').substring(2)}';
          final pattern = RegExp('(id="$targetId"[^>]*?)(fill="[^"]*")', multiLine: true);
          
          svgContent = svgContent.replaceAllMapped(pattern, (match) {
            final prefix = match.group(1) ?? '';
            return '$prefix fill="$hexColor"';
          });
        });

        return SvgPicture.string(
          svgContent,
          width: width,
          height: height,
          fit: BoxFit.contain,
        );
      },
    );
  }
}

使用方法

このウィジェットは以下のように使用できます:

// 単一の色を変更する場合
ColorChangeSvg(
  assetName: 'assets/images/example.svg',
  colorMapping: {'change_color_target': Colors.blue},
  width: 150,
  height: 150,
)

// 複数の色を変更する場合
ColorChangeSvg(
  assetName: 'assets/images/example.svg',
  colorMapping: {
    'target_1': Colors.yellow,
    'target_2': Colors.red,
    'target_3': Colors.green,
  },
  width: 150,
  height: 150,
)

パラメータの説明

  • assetName: SVGファイルのアセットパス

    • 例: assets/images/example.svg
    • pubspec.yamlに登録したパスと一致している必要があります
  • colorMapping: 変更したい要素のIDと色のマップ

    • キー: SVGファイル内の要素のID
    • 値: 変更後の色(Colorsクラスの色)
    • 単一の色変更の場合も、複数の色変更の場合も同じ形式で指定できます
  • widthheight: SVGの表示サイズ

    • 数値で指定(例: 150
    • 単位はデバイスの論理ピクセルです
    • アスペクト比はBoxFit.containで自動調整されます

デモページの実装例

下記のsvgファイルを使用します。

練習用にそのまま使用してOK

example.svg
<svg xmlns="http://www.w3.org/2000/svg" width="888" height="483.61099" viewBox="0 0 888 483.61099" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="m1,421.53481h887v-2H1c-.55228,0-1,.44772-1,1h0c0,.55228.44771,1,1,1Z" fill="#3f3d56"/><path d="m406,438.53481c5,24,33.5-5.5,33.5-5.5l34-4s21.5.5,35.5,11.5,23.5-3.5,23.5-3.5c15.77283,5.2576,35.09692,5.23627,51.83264,3.55252,24.11597-2.42627,43.44604-21.04041,46.76044-45.05038,18.69678-135.44147-84.09314-202.00211-84.09314-202.00211l-30-81s9.5-23.5,12.5-44.5-19-41-46-61c-11.8125-8.75-25.34766-8.12109-36.83716-4.64502-12.88892,3.89946-24.13214,11.9183-32.4397,22.51616-6.88251,8.77995-18.68735,21.21468-31.66315,23.36885-4.48999.73999-8.91998,2.52002-13.06,5.76001-4.28003,3.34998-6.16998,8.94-6.22998,15.89001-.32001,30.35999,24.75,66.90998,46.72998,72.60999,27,7,12.5,73.5,12.5,73.5-44,62-13,131-13,131l-8,56s-.5,11.5,4.5,35.5l.00006-.00003Z" fill="#3f3d56"/><path d="m553,309.53481l-11,137s15,38-36,36-21-80-21-80l16-102" fill="#3f3d56"/><path d="m509.79492,483.61099c-1.24707,0-2.52344-.02539-3.83398-.07715-11.29004-.44238-19.60352-4.60059-24.71094-12.3584-14.60254-22.18164,1.63867-65.90625,2.77832-68.89941l15.9834-101.89648c.08594-.5459.59375-.91309,1.14355-.83301.54492.08594.91797.59766.83301,1.14355l-16,102c-.01172.06934-.0293.1377-.05469.2041-.1748.45215-17.25195,45.55566-3.01172,67.18359,4.73242,7.18652,12.50977,11.04199,23.11719,11.45801,16.65234.64941,27.85254-2.90332,33.29785-10.56836,7.12402-10.02832,1.78711-23.92676,1.73242-24.06641-.05566-.1416-.07812-.29492-.06641-.44629l11-137c.04395-.5498.5166-.95117,1.07715-.91699.55078.04492.96094.52637.91699,1.07715l-10.98242,136.77539c.72656,1.96094,5.28223,15.4043-2.03906,25.72363-5.41699,7.63574-15.89844,11.49707-31.18066,11.49707Z" fill="#2f2e41"/><path d="m462,337.53481s3,127-60,124-6-43-6-43l5.5-15.5" fill="#3f3d56"/><path d="m404.16211,462.58657c-.73145,0-1.4668-.01758-2.20996-.05273-17.98828-.85645-28.15186-4.88672-30.20801-11.97852-3.7041-12.7793,20.43945-30.52148,23.41895-32.65332l5.39453-15.20117c.18457-.52148.75586-.79297,1.27637-.6084s.79297.75586.6084,1.27637l-5.5,15.5c-.06934.19629-.19824.36523-.36816.48438-.26367.18555-26.37744,18.68457-22.90869,30.64551,1.79248,6.18066,11.34131,9.72559,28.38232,10.53711,11.34863.5459,21.29688-3.27051,29.55078-11.3291,30.95215-30.21582,29.4209-110.83789,29.40137-111.64844-.0127-.55176.42383-1.00977.97656-1.02344.52246.00098,1.00977.4248,1.02344.97656.0791,3.3457,1.57227,82.29785-30.00293,113.125-8.12793,7.93555-17.82031,11.9502-28.83496,11.9502Z" fill="#2f2e41"/><path d="m363.27002,69.92484c7.17999.85999,18.41998.81995,27.72998-5.39001,9.96997-6.65002-.38-12.85004-8.44-16.26001-4.48999.73999-8.91998,2.52002-13.06,5.76001-4.28003,3.34998-6.16998,8.94-6.22998,15.89001Z" fill="#2f2e41"/><path id="target_2" d="m479.2984,51.94388s-17.44968-44.81639,7.54245-45.4435,44.73257,38.20994,44.73257,38.20994c0,0,62.72797,114.34248-23.21378,108.17637-85.94171-6.16615-44.87375-51.81067-44.87375-51.81067,0,0,19.60165-24.09928,15.81253-49.13211h-.00003v-.00002h0s0-.00002,0-.00002Z" fill="#2f2e41"/><path id="target_3" d="m575.03857,386.85064c11.06787,4.92365,22.63171,9.948,34.73578,9.46738s24.84155-8.27557,26.72772-20.24146c.97363-6.17697-.95746-12.80396,1.40759-18.59268,3.18213-7.78867,13.44672-10.79233,21.42578-8.12323s13.84076,9.55188,18.04358,16.84058c7.86328,13.63672,11.0141,31.53461,2.58307,44.82779-7.30878,11.52368-21.17084,16.73105-34.06519,21.19748-17.17535,5.94928-36.35114,11.854-52.99396,4.54617-16.7381-7.34964-25.85565-28.58435-19.65753-45.7821" fill="#3f3d56"/><path id="target_1" d="m538.1142,170.92143s22,11,11,23-79,46-104,41-31-20-31-24,1.88583-52.38661,9.88583-50.38661,58.11417,47.38661,114.11417,10.38661Z" fill="#00bfa6"/></svg>

実際の使用例はこちら:

import 'package:flutter/material.dart';
import 'package:knocqnow/presentation/components/common/color_change_svg.dart';

/// SVGの色変更機能のデモページ
class SvgMockPage extends StatelessWidget {
  const SvgMockPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SVG Color Change Demo'),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Single Color Change'),
            SizedBox(height: 16),
            ColorChangeSvg(
              assetName: 'assets/images/example.svg',
              colorMapping: {'target_1': Colors.blue},
              width: 200,
              height: 200,
            ),
            SizedBox(height: 32),
            Text('Multi Color Change'),
            SizedBox(height: 16),
            ColorChangeSvg(
              assetName: 'assets/images/example.svg',
              colorMapping: {
                'target_1': Colors.yellow,
                'target_2': Colors.red,
                'target_3': Colors.green,
              },
              width: 200,
              height: 200,
            ),
          ],
        ),
      ),
    );
  }
}

SVGファイルで色を変更したい要素には、必ず対応するid属性を指定する必要があります。
例えば、colorMappingtarget_1を指定する場合、SVGファイル内にid="target_1"の要素が存在している必要があります。

完成イメージはこちら

Screenshot 2024-11-28 at 10.24.03 PM.png

あとは colorMapping: {'target_1': Colors.blue}, の色の部分を
現在使用中のアプリのテーマカラー用の変数に変更してあげれば、
テーマカラーに合わせてSVGファイルの色を動的に変更可能!

トラブルシューティング

色変更が正しく反映されない場合は、以下の点を確認してください:

  1. SVGファイルがpubspec.yamlassetsに正しく登録されているか
  2. SVGファイル内の要素に適切なid属性が設定されているか
  3. colorMappingで指定したIDがSVGファイル内のIDと完全に一致しているか

問題が解決しない場合は、以下のコマンドを実行してキャッシュをクリアしてください。
一度でうまくいかない場合は何回か試してみてください:

flutter clean
flutter pub get

まとめ

このウィジェットを使用することで、SVGファイルの色を動的に変更できるようになります。
是非お試しください。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?