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; } }
目前存在两个问题:
- 自定义绘制的组件无法适配外层Container,宽度总是超出容器边界
- 无法同时实现双击缩放、捏合缩放和平移功能:使用
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
相关产品推荐
相关产品推荐

