You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Flutter与flutter_svg动态为SVG特定部位上色?

动态为Flutter SVG中特定肌肉路径上色的实现方案

核心思路

利用flutter_svg加载SVG的特性,结合SVG字符串修改或XML DOM解析,针对带有唯一ID的肌肉路径节点,动态替换其fill颜色属性,再重新渲染SVG即可实现需求。

具体实现步骤

  1. 准备原始SVG资源(可作为字符串常量,或从本地assets读取后转为字符串)
  2. 维护状态变量记录各肌肉ID对应的颜色值
  3. 通过字符串替换或XML解析修改SVG中对应路径的填充色
  4. 用SvgPicture.string加载修改后的SVG字符串
  5. 提供交互控件(如颜色选择器)更新颜色状态,触发UI重绘

代码示例

方案1:正则表达式替换(轻量场景)

适合SVG结构简单、style格式统一的情况:

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

// 原始SVG字符串(简化版)
const String originalSvg = '''
<svg width="5000" height="5000" xmlns="http://www.w3.org/2000/svg">
  <path
         id="muscle1"
         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
         d="M 5000,0 H 0 V 5000 H 5000 V 0" />
  <path
         id="muscle2"
         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
         d="M 1000,1000 H 2000 V 2000 H 1000 V 1000" />
</svg>
''';

class MuscleColorEditor extends StatefulWidget {
  const MuscleColorEditor({super.key});

  @override
  State<MuscleColorEditor> createState() => _MuscleColorEditorState();
}

class _MuscleColorEditorState extends State<MuscleColorEditor> {
  // 存储肌肉ID与对应颜色的映射
  final Map<String, String> _muscleColors = {
    'muscle1': '#ffffff',
    'muscle2': '#ffffff',
  };

  // 生成带颜色的SVG字符串
  String _generateColoredSvg() {
    String modifiedSvg = originalSvg;
    _muscleColors.forEach((muscleId, color) {
      // 匹配对应ID路径的style属性,替换fill值
      final regex = RegExp(r'id="'+muscleId+r'"[^>]+style="([^"]*)fill:#[0-9a-fA-F]+;([^"]*)"');
      modifiedSvg = modifiedSvg.replaceAllMapped(regex, (match) {
        return 'id="$muscleId" style="${match.group(1)}fill:$color;${match.group(2)}"';
      });
    });
    return modifiedSvg;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('肌肉上色编辑器')),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: SvgPicture.string(
                _generateColoredSvg(),
                fit: BoxFit.contain,
              ),
            ),
          ),
          // 颜色选择交互区
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _buildColorPickerButton('muscle1', '肌肉1'),
              _buildColorPickerButton('muscle2', '肌肉2'),
            ],
          ),
          const SizedBox(height: 20),
        ],
      ),
    );
  }

  // 构建颜色选择按钮
  Widget _buildColorPickerButton(String muscleId, String label) {
    return ElevatedButton(
      onPressed: () async {
        final Color? pickedColor = await showDialog<Color>(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('选择$label颜色'),
            content: ColorPicker(
              pickerColor: Color(int.parse(_muscleColors[muscleId]!.replaceAll('#', '0xff'))),
              onColorChanged: (color) {},
              showLabel: true,
              pickerAreaHeightPercent: 0.8,
            ),
            actions: [
              TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
              TextButton(
                onPressed: () {
                  final currentColor = Color(int.parse(_muscleColors[muscleId]!.replaceAll('#', '0xff')));
                  Navigator.pop(context, currentColor);
                },
                child: const Text('确认'),
              ),
            ],
          ),
        );
        if (pickedColor != null) {
          setState(() {
            _muscleColors[muscleId] = '#${pickedColor.value.toRadixString(16).substring(2)}';
          });
        }
      },
      child: Text(label),
    );
  }
}

方案2:XML DOM解析(复杂场景推荐)

当SVG结构复杂、style格式不固定时,使用xml包解析DOM更可靠:

  1. 先在pubspec.yaml添加依赖:
dependencies:
  xml: ^6.5.0
  1. 核心修改代码:
import 'package:xml/xml.dart';

String _generateColoredSvg() {
  final document = XmlDocument.parse(originalSvg);
  _muscleColors.forEach((muscleId, color) {
    // 定位对应ID的path节点
    final pathElement = document.findAllElements('path').firstWhere(
      (element) => element.getAttribute('id') == muscleId,
      orElse: () => throw Exception('未找到ID为$muscleId的肌肉路径'),
    );
    // 修改style中的fill属性
    final style = pathElement.getAttribute('style') ?? '';
    final newStyle = style.replaceAll(RegExp(r'fill:#[0-9a-fA-F]+;'), 'fill:$color;');
    pathElement.setAttribute('style', newStyle);
  });
  return document.toXmlString();
}

注意事项

  • 确保所有肌肉路径都有唯一ID,这是准确定位的核心
  • 若SVG体积较大,可考虑缓存修改后的SVG字符串或Picture对象,减少重复解析开销
  • 正则表达式需根据实际SVG的style格式调整,避免匹配错误

内容的提问来源于stack exchange,提问作者Ghina Sharaf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:20:17