99 lines
3.3 KiB
Dart
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;
|
|
}
|
|
}
|