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

如何在Flutter中实现交互式SVG世界地图?

Flutter实现可交互世界地图SVG:点击高亮+提取国家名称

1. 准备SVG资源

先把目标世界地图SVG下载到本地项目,比如存放在assets/maps/world_map.svg路径下,然后在pubspec.yaml中配置资源引用:

flutter:
  assets:
    - assets/maps/world_map.svg

注意:下载的SVG里每个国家的路径元素需要带有标识属性(比如id、data-name,这类属性通常是原SVG自带的),这是后续识别国家的核心依据。

2. 添加依赖

使用flutter_svg包来解析和交互SVG,在pubspec.yaml中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_svg: ^2.0.9 # 建议使用最新稳定版

3. 核心代码实现

3.1 状态管理基础组件

创建一个StatefulWidget来管理选中国家的状态:

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

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

  @override
  State<InteractiveWorldMap> createState() => _InteractiveWorldMapState();
}

class _InteractiveWorldMapState extends State<InteractiveWorldMap> {
  String? selectedCountryName;
  String? selectedCountryId;

  // 处理路径点击,提取国家信息
  void _onPathTap(String id, Map<String, String> attributes) {
    setState(() {
      selectedCountryId = id;
      selectedCountryName = attributes['data-name']; // 从SVG属性中获取国家名称
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: SvgPicture.asset(
            'assets/maps/world_map.svg',
            fit: BoxFit.contain,
            semanticsLabel: '世界地图',
            builder: (context, svgWidget) {
              return InteractiveViewer( // 添加缩放功能,可选
                child: _SvgInteractiveWrapper(
                  svgWidget: svgWidget,
                  selectedCountryId: selectedCountryId,
                  onPathTap: _onPathTap,
                ),
              );
            },
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            selectedCountryName != null 
                ? '选中国家: $selectedCountryName' 
                : '点击任意国家',
            style: const TextStyle(fontSize: 18),
          ),
        ),
      ],
    );
  }
}

3.2 自定义SVG交互包装器

遍历SVG的路径元素,添加点击事件:

class _SvgInteractiveWrapper extends StatelessWidget {
  final Widget svgWidget;
  final String? selectedCountryId;
  final Function(String id, Map<String, String> attributes) onPathTap;

  const _SvgInteractiveWrapper({
    required this.svgWidget,
    required this.selectedCountryId,
    required this.onPathTap,
  });

  @override
  Widget build(BuildContext context) {
    return Builder(builder: (context) => _traverseSvgTree(svgWidget));
  }

  Widget _traverseSvgTree(Widget widget) {
    // 递归遍历SVG的Widget树
    if (widget is Container && widget.child != null) {
      return Container(
        decoration: widget.decoration,
        padding: widget.padding,
        child: _traverseSvgTree(widget.child!),
      );
    }

    if (widget is CustomPaint && widget.painter is SvgPainter) {
      final painter = widget.painter as SvgPainter;
      return GestureDetector(
        onTapUp: (details) {
          final localPos = details.localPosition;
          // 检测点击命中的路径
          for (final element in painter.currentRoot.children) {
            if (element is SvgPathElement) {
              final path = element.path;
              if (path.contains(localPos)) {
                final attrs = element.attributes;
                onPathTap(attrs['id'] ?? '', attrs);
                break;
              }
            }
          }
        },
        child: CustomPaint(
          painter: _HighlightSvgPainter(
            originalPainter: painter,
            selectedCountryId: selectedCountryId,
          ),
          size: widget.size,
        ),
      );
    }

    if (widget is Stack) {
      return Stack(children: widget.children.map(_traverseSvgTree).toList());
    }

    return widget;
  }
}

3.3 高亮绘制器

自定义Painter实现选中国家的高亮效果:

class _HighlightSvgPainter extends CustomPainter {
  final SvgPainter originalPainter;
  final String? selectedCountryId;

  _HighlightSvgPainter({
    required this.originalPainter,
    required this.selectedCountryId,
  });

  @override
  void paint(Canvas canvas, Size size) {
    // 先绘制原始SVG
    originalPainter.paint(canvas, size);

    if (selectedCountryId == null) return;

    // 给选中的国家路径添加高亮
    for (final element in originalPainter.currentRoot.children) {
      if (element is SvgPathElement && element.attributes['id'] == selectedCountryId) {
        final path = element.path;
        // 半透明填充高亮
        final fillPaint = Paint()
          ..color = Colors.red.withOpacity(0.3)
          ..style = PaintingStyle.fill;
        canvas.drawPath(path, fillPaint);

        // 红色描边强化边界
        final strokePaint = Paint()
          ..color = Colors.red
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2;
        canvas.drawPath(path, strokePaint);
        break;
      }
    }
  }

  @override
  bool shouldRepaint(_HighlightSvgPainter oldDelegate) {
    return oldDelegate.selectedCountryId != selectedCountryId;
  }
}

4. 注意事项

  • 确认SVG中每个国家路径都带有id和data-name属性,若缺失需手动编辑SVG补充。
  • 可根据需求调整高亮的颜色、透明度、描边宽度等样式。
  • 若SVG文件较大,可优化路径命中检测逻辑(比如提前缓存路径边界盒),提升点击响应速度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 18:32:28