import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; /// Egyenletes, ANIMÁLT térkép-követés — a `mapController.move()`/ /// `.rotate()` közvetlen hívása helyett, amik AZONNAL ugranak az új /// pozícióra/irányba. /// /// Ez a widget semmit nem jelenít meg (0 méretű) — kizárólag arra való, /// hogy a [target] (és opcionálisan [heading]) VÁLTOZÁSAKOR sima /// animációval, ne ugrással vigye át a térképet a régi pontból az újba. /// /// Belül a Flutter beépített `TweenAnimationBuilder`-ét használja — ez /// IMPLICIT módon animál (a cél-érték változásakor magától, a jelenlegi /// állásból indulva animál az újig). A ticker a Flutter-widget /// belsejében él, nekünk nem kell saját State/AnimationController-t /// írnunk hozzá — ez az EGYETLEN pont, ahol animáció miatt egyáltalán /// szóba kerül egy ticker, de azt is a keretrendszer adja. class AnimatedMapFollow extends StatelessWidget { final MapController mapController; final LatLng? target; final double? heading; // fok — ha null, nincs forgatás final Duration duration; const AnimatedMapFollow({ super.key, required this.mapController, required this.target, this.heading, this.duration = const Duration(milliseconds: 400), }); @override Widget build(BuildContext context) { final t = target; if (t == null) return const SizedBox.shrink(); return Stack( children: [ TweenAnimationBuilder( tween: _LatLngTween(begin: t, end: t), duration: duration, curve: Curves.easeOut, builder: (context, animatedPos, _) { try { mapController.move(animatedPos, mapController.camera.zoom); } catch (_) {} return const SizedBox.shrink(); }, ), if (heading != null) TweenAnimationBuilder( // A térkép a haladási irány ELLENTETTJÉVEL forog (track-up). tween: _DegreesTween(begin: -heading!, end: -heading!), duration: duration, curve: Curves.easeOut, builder: (context, animatedRotation, _) { try { mapController.rotate(animatedRotation); } catch (_) {} return const SizedBox.shrink(); }, ), ], ); } } class _LatLngTween extends Tween { _LatLngTween({required LatLng begin, required LatLng end}) : super(begin: begin, end: end); @override LatLng lerp(double t) { final b = begin!, e = end!; return LatLng( b.latitude + (e.latitude - b.latitude) * t, b.longitude + (e.longitude - b.longitude) * t, ); } } /// Fok-interpoláció a "legrövidebb úton" — enélkül egy 350°→10° váltás /// hosszan, "visszafelé" animálna 340°-ot ahelyett hogy egyenesen 20°-ot /// lépne előre. class _DegreesTween extends Tween { _DegreesTween({required double begin, required double end}) : super(begin: begin, end: end); @override double lerp(double t) { final b = begin!, e = end!; var diff = (e - b) % 360; if (diff > 180) diff -= 360; if (diff < -180) diff += 360; return b + diff * t; } }