Files
MobilApp/lib/widgets/map/animated_map_follow.dart
T

99 lines
3.3 KiB
Dart

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<LatLng>(
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<double>(
// 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<LatLng> {
_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<double> {
_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;
}
}