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

Flutter中Google Map圆形波纹动画导致应用卡顿问题排查

Google Map Flutter 动画卡顿问题排查与解决

问题描述

在Flutter中使用Google Map组件实现多个圆形的缩放动画时,应用出现明显卡顿。尝试使用flutter_animarker库后问题依旧,不清楚是Google Map插件本身不支持动画,还是实现方式存在错误。

卡顿原因分析

从提供的代码来看,卡顿的核心原因在于不必要的全量重建和对象重复创建:

  1. AnimatedBuilder包裹整个GoogleMap:动画控制器每帧触发时,会重建整个GoogleMap组件,包括所有Marker和Circle,导致UI线程负载过高。
  2. Circle/Marker对象重复生成:每次动画帧都通过map方法重新创建所有Circle和Marker实例,大量临时对象会触发频繁GC,进一步加剧卡顿。
  3. 无限循环动画持续触发重建:AnimationController设置了repeat(),每800ms循环一次,持续触发全量重建,没有针对性的局部更新逻辑。

解决方案

1. 缩小动画更新范围

用ValueNotifier+ValueListenableBuilder替代AnimatedBuilder,仅针对需要动画的Circle部分做局部更新,避免整个GoogleMap重建:

2. 复用Circle对象

通过copyWith方法复用已有的Circle实例,只更新动画相关的属性(半径、透明度),减少对象创建开销。

修改后的代码示例

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

  @override
  State<GoogleMapInteg> createState() => _GoogleMapIntegState();
}

class _GoogleMapIntegState extends State<GoogleMapInteg>
    with SingleTickerProviderStateMixin {
  final CameraPosition ciaro =
      const CameraPosition(target: LatLng(30.0455, 31.2360), zoom: 12.4746);

  late GoogleMapController? gmcontroller;
  bool loading = true;
  double radius = 1500;
  late AnimationController animationController;
  late ValueNotifier<double> animationValueNotifier;
  Set<Circle> circles = {};

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    )..repeat();
    
    // 用ValueNotifier管理动画值,仅更新需要变化的部分
    animationValueNotifier = ValueNotifier(0.0);
    animationController.addListener(() {
      animationValueNotifier.value = animationController.value;
    });
    
    _delay();
  }

  void _delay() {
    Future.delayed(const Duration(milliseconds: 500), () => setState(() => loading = false));
  }

  @override
  void dispose() {
    animationController.dispose();
    animationValueNotifier.dispose();
    gmcontroller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: !loading
          ? Consumer<StationController>(
              builder: (context, value, child) {
                return GoogleMap(
                  initialCameraPosition: ciaro,
                  zoomControlsEnabled: true,
                  // 仅在动画值变化时更新Circles
                  circles: ValueListenableBuilder<double>(
                    valueListenable: animationValueNotifier,
                    builder: (context, animValue, _) {
                      return value.stations.map<Circle>((e) {
                        final circleId = CircleId(e.name!);
                        // 查找已存在的Circle,复用并更新属性
                        final existingCircle = circles.firstWhere(
                          (c) => c.circleId == circleId,
                          orElse: () => Circle(
                            circleId: circleId,
                            center: e.location!,
                            strokeColor: Colors.transparent,
                            strokeWidth: 1,
                            visible: true,
                          ),
                        );
                        return existingCircle.copyWith(
                          radiusParam: radius * animValue,
                          fillColorParam: kPrimaryColor.withOpacity((animValue - 1.0).abs()),
                        );
                      }).toSet()..forEach((circle) => circles.add(circle));
                    },
                  ),
                  markers: value.stations.map<Marker>((element) => Marker(
                    markerId: MarkerId(element.name!),
                    position: element.location!,
                    consumeTapEvents: true,
                    icon: element.selected ? element.iconNotSelected : element.iconSelected,
                    onTap: () => value.addStation(element.name),
                  )).toSet(),
                  onMapCreated: (GoogleMapController controller) {
                    gmcontroller = controller;
                    gmcontroller!.setMapStyle(jsonEncode(mapStyle));
                  },
                );
              },
            )
          : const Center(
              child: CircularProgressIndicator(color: kPrimaryColor, strokeWidth: 2.0),
            ),
    );
  }
}

额外优化建议

  • 若不需要无限循环动画,移除animationController.repeat(),改为按需触发动画。
  • 减少动画帧频率:适当延长动画时长,降低每秒钟的重建次数。
  • 对Marker也做类似复用优化:如果Marker状态不频繁变化,避免每次重建都重新生成。

关于flutter_animarker:该库主要针对Marker的路径移动动画做优化,对于Circle的缩放场景适配性有限,仍需通过上述局部更新逻辑优化性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 22:20:26