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

Flutter CustomPainter绘制地图适配容器及缩放平移功能实现问题

Flutter CustomPainter 行政区地图组件适配及交互问题解决

你通过CustomPainter开发行政区地图展示组件,使用path_drawing库解析SVG路径生成Path对象,借助touchable库实现画布内路径的点击交互,现有绘制代码如下:

class MyPainter extends CustomPainter {
  Function(String) setState;
  final BuildContext context;

  MyPainter(this.context, this.setState);

  @override
  void paint(Canvas _canvas, Size size) {
    TouchyCanvas canvas = TouchyCanvas(context, _canvas);

    // _canvas.translate(0, 0);

    districtMapData.forEach((e) {
      var _path = parseSvgPathData(e['shape'].toString());

      canvas.drawPath(
          _path,
          Paint()
            ..color = Colors.black
            ..strokeWidth = 200
            ..style = PaintingStyle.fill, onTapDown: (a) {
        print("pressed");
        print(e['name']);
      }, onTapUp: (a) {
        print("pressed up");
      });
    });
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

目前存在两个问题:

  1. 自定义绘制的组件无法适配外层Container,宽度总是超出容器边界
  2. 无法同时实现双击缩放、捏合缩放和平移功能:使用zoom_widget库仅支持双击缩放,平移、捏合手势无效;尝试Flutter原生InteractiveViewer也未生效

页面原有实现代码如下:

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

import 'package:touchable/touchable.dart';
import 'package:zoom_widget/zoom_widget.dart';

class ZoomableMapView extends StatefulWidget {
  @override
  _ZoomableMapViewState createState() => _ZoomableMapViewState();
}

class _ZoomableMapViewState extends State<ZoomableMapView>
    with SingleTickerProviderStateMixin {
  Color color = Colors.pink;

  @override
  Widget build(BuildContext context) {
    final _width = MediaQuery.of(context).size.width;

    return Scaffold(
        body: SafeArea(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          color: Colors.red,
          child: AnimatedContainer(
              duration: Duration(seconds: 1),
              width: _width,
              height: 400,
              child:
                  // InteractiveViewer
                  Zoom(
                // minScale: 0.1,
                // maxScale: 5,
                // alignPanAxis: false,

                maxZoomHeight: 1000,
                maxZoomWidth: 1000,
                initZoom: 0.1,
                // centerOnScale: false,
                child: Transform.rotate(
                  angle: -pi / 8,
                  child: CanvasTouchDetector(
                    builder: (context) {
                      return CustomPaint(
                        painter: MyPainter(context, (String circleColor) {}),
                      );
                    },
                  ),
                ),
              )),
        ),
      ),
    ));
  }
}

解决方案

问题1:画布适配外层容器

问题根源是解析得到的SVG路径为原始固定尺寸,没有根据父容器尺寸做动态缩放适配,修改MyPainter的paint方法如下:

@override
void paint(Canvas _canvas, Size size) {
  TouchyCanvas canvas = TouchyCanvas(context, _canvas);
  // 先计算所有路径的总外接边界
  Rect totalBounds = Rect.zero;
  List<MapEntry<Path, Map>> pathList = [];
  districtMapData.forEach((e) {
    Path path = parseSvgPathData(e['shape'].toString());
    pathList.add(MapEntry(path, e));
    totalBounds = totalBounds.expandToInclude(path.getBounds());
  });
  // 计算适配容器的等比缩放比例
  double scaleX = size.width / totalBounds.width;
  double scaleY = size.height / totalBounds.height;
  double scale = min(scaleX, scaleY);
  // 计算平移量让地图在容器内居中
  double offsetX = (size.width - totalBounds.width * scale) / 2 - totalBounds.left * scale;
  double offsetY = (size.height - totalBounds.height * scale) / 2 - totalBounds.top * scale;
  // 对画布做统一变换
  canvas.translate(offsetX, offsetY);
  canvas.scale(scale);
  // 绘制所有路径
  pathList.forEach((entry) {
    Path _path = entry.key;
    Map e = entry.value;
    canvas.drawPath(
      _path,
      Paint()
        ..color = Colors.black
        ..strokeWidth = 200 / scale // 线宽同步反向缩放,保证视觉线宽一致
        ..style = PaintingStyle.fill, 
      onTapDown: (a) {
        print("pressed");
        print(e['name']);
      }, 
      onTapUp: (a) {
        print("pressed up");
      }
    );
  });
}

问题2:同时支持双击、捏合缩放+平移

InteractiveViewer无效的核心原因是未给CustomPaint设置明确尺寸,同时替换第三方zoom_widget为原生InteractiveViewer即可,修改build方法如下:

@override
Widget build(BuildContext context) {
  final _width = MediaQuery.of(context).size.width;
  return Scaffold(
    body: SafeArea(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          color: Colors.red,
          child: AnimatedContainer(
            duration: Duration(seconds: 1),
            width: _width,
            height: 400,
            child: InteractiveViewer(
              minScale: 0.1,
              maxScale: 5.0,
              panEnabled: true,
              scaleEnabled: true,
              child: Transform.rotate(
                angle: -pi / 8,
                child: CanvasTouchDetector(
                  builder: (context) {
                    // 给CustomPaint明确设置尺寸为父容器尺寸
                    return CustomPaint(
                      size: Size(_width, 400),
                      painter: MyPainter(context, (String circleColor) {}),
                    );
                  },
                ),
              ),
            )
          ),
        ),
      ),
    )
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 21:06:05