1.优化了无人的轨迹的绘制的过程中的 飞机不动路线自己动的不和谐现象,实现了飞机实时根据推送的数据绘制珍格格飞机任务的过程,同时也优化了地图加载慢 白底色的问题。
2.完善了遗留一些页面的多语言支持的。
This commit is contained in:
@@ -19,6 +19,14 @@ import '../../../../../../core/managers/drone_task_state_manager.dart';
|
||||
import '../../../../../../core/network/mqtt/domain/entities/drone_osd_entity.dart';
|
||||
import '../../../../../../core/network/mqtt/data/datasources/drone_osd_datasource.dart';
|
||||
|
||||
/// 模拟轨迹模式枚举
|
||||
enum SimulationMode {
|
||||
circle, // 圆形路径
|
||||
polygon, // 多边形路径(六边形)
|
||||
bow, // 弓字形路径(Zigzag)
|
||||
rectangle, // 矩形路径
|
||||
}
|
||||
|
||||
/// 可拖动的悬浮按钮组件
|
||||
/// 点击弹出底部弹窗显示设备状态详情
|
||||
class FloatBarWidget extends StatefulWidget {
|
||||
@@ -69,6 +77,9 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
|
||||
MapController? _mapController;
|
||||
bool _isUserDragging = false;
|
||||
|
||||
/// 用来同步刷新弹窗内部的 State
|
||||
void Function(void Function())? _bottomSheetSetState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -89,6 +100,7 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
_droneOsdDataSource?.dispose();
|
||||
_mapController?.dispose();
|
||||
_destroyRtcEngine();
|
||||
_simulationTimer?.cancel();
|
||||
droneTaskStateManager.currentTaskInfo.removeListener(_onTaskInfoChanged);
|
||||
super.dispose();
|
||||
}
|
||||
@@ -159,7 +171,10 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
|
||||
/// 初始化无人机相关组件
|
||||
void _initDroneComponents() {
|
||||
if (_droneTaskInfo == null) return;
|
||||
if (_droneTaskInfo == null) {
|
||||
debugPrint('❌ [FloatBarWidget] _droneTaskInfo 为空,无法初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('🛸 [FloatBarWidget] 初始化无人机组件');
|
||||
debugPrint(' droneSn: ${_droneTaskInfo!.droneSn}');
|
||||
@@ -174,30 +189,34 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
|
||||
// 初始化地图控制器
|
||||
_mapController = MapController();
|
||||
|
||||
// 立即设置初始位置,确保飞机能立即显示
|
||||
setState(() {
|
||||
_currentPosition = const LatLng(39.9042, 116.4074);
|
||||
_currentHeading = 90;
|
||||
_trajectoryPoints = [const LatLng(39.9042, 116.4074)];
|
||||
});
|
||||
|
||||
// 启动模拟轨迹数据(用于测试)
|
||||
_startSimulationTrajectory();
|
||||
debugPrint('✅ [FloatBarWidget] 地图控制器已初始化');
|
||||
|
||||
// 只有在线时才启动 OSD 监听和视频流加载
|
||||
if (_droneTaskInfo!.isOnline) {
|
||||
debugPrint('🟢 [FloatBarWidget] 无人机在线,启动完整功能');
|
||||
|
||||
// 初始化 OSD 数据源
|
||||
_droneOsdDataSource = sl<DroneOsdDataSource>();
|
||||
_startOsdListening();
|
||||
|
||||
// 加载视频流
|
||||
_loadVideoStream();
|
||||
|
||||
// 🔥 启动模拟轨迹用于测试(可以选择不同模式)
|
||||
// 模式选项: circle(圆形)、polygon(六边形)、bow(弓字形)、rectangle(矩形)
|
||||
// 等天气好了,注释掉下面这行即可使用真实 MQTT 数据
|
||||
debugPrint('🚀 [FloatBarWidget] 准备启动模拟轨迹...');
|
||||
_startSimulationTrajectory(mode: SimulationMode.circle, pointCount: 30);
|
||||
} else {
|
||||
debugPrint('⚠️ [FloatBarWidget] 无人机离线,不启动 OSD 监听和视频流');
|
||||
debugPrint('🚀 [FloatBarWidget] 但仍然启动模拟轨迹用于测试...');
|
||||
|
||||
setState(() {
|
||||
_videoError = '无人机离线,无视频信号';
|
||||
});
|
||||
|
||||
// 离线时也启动模拟轨迹,方便测试
|
||||
_startSimulationTrajectory(mode: SimulationMode.circle, pointCount: 30);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,129 +244,311 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
|
||||
/// 处理 OSD 数据更新
|
||||
void _handleOsdUpdate(DroneOsdEntity osd) {
|
||||
// ⚠️ 调试期间:忽略 MQTT 数据,只看模拟轨迹
|
||||
if (_simulationTimer != null && _simulationTimer!.isActive) {
|
||||
debugPrint('⚠️ [FloatBarWidget] 模拟轨迹运行中,忽略 MQTT 数据');
|
||||
return;
|
||||
}
|
||||
|
||||
final rawData = osd.rawData;
|
||||
|
||||
// 获取经纬度
|
||||
double? latitude = (rawData['latitude'] as num?)?.toDouble();
|
||||
double? longitude = (rawData['longitude'] as num?)?.toDouble();
|
||||
double? heading = (rawData['heading'] as num?)?.toDouble();
|
||||
debugPrint('📍 [FloatBarWidget] 收到 OSD 数据: ${rawData.keys.join(', ')}');
|
||||
|
||||
if (latitude != null &&
|
||||
longitude != null &&
|
||||
latitude != 0 &&
|
||||
longitude != 0) {
|
||||
// 1. 解析基础数据
|
||||
double? lat = (rawData['latitude'] as num?)?.toDouble() ??
|
||||
(rawData['lat'] as num?)?.toDouble();
|
||||
double? lng = (rawData['longitude'] as num?)?.toDouble() ??
|
||||
(rawData['lng'] as num?)?.toDouble() ??
|
||||
(rawData['lon'] as num?)?.toDouble();
|
||||
double? heading = (rawData['heading'] as num?)?.toDouble() ??
|
||||
(rawData['yaw'] as num?)?.toDouble();
|
||||
|
||||
if (lat != null && lng != null && lat != 0 && lng != 0) {
|
||||
final newPos = LatLng(lat, lng);
|
||||
|
||||
setState(() {
|
||||
_currentPosition = LatLng(latitude, longitude);
|
||||
_currentHeading = heading;
|
||||
// 计算航向:如果 MQTT 没给角度,我们根据上一个点自动算一个方向
|
||||
if (heading != null) {
|
||||
_currentHeading = heading;
|
||||
} else if (_trajectoryPoints.isNotEmpty) {
|
||||
final last = _trajectoryPoints.last;
|
||||
// 使用 atan2 计算方位角
|
||||
_currentHeading = atan2(lng - last.longitude, lat - last.latitude) * 180 / pi;
|
||||
}
|
||||
|
||||
// 添加到轨迹点
|
||||
if (_currentPosition != null) {
|
||||
_trajectoryPoints.add(_currentPosition!);
|
||||
// 限制轨迹点数量,避免内存问题
|
||||
if (_trajectoryPoints.length > 500) {
|
||||
_trajectoryPoints.removeAt(0);
|
||||
// 更新当前位置(驱动飞机 Marker)
|
||||
_currentPosition = newPos;
|
||||
|
||||
// 轨迹“拉烟”逻辑:距离过滤(防止 GPS 抖动导致轨迹像乱麻)
|
||||
if (_trajectoryPoints.isEmpty) {
|
||||
_trajectoryPoints.add(newPos);
|
||||
} else {
|
||||
final distance = _calculateDistance(
|
||||
_trajectoryPoints.last.latitude,
|
||||
_trajectoryPoints.last.longitude,
|
||||
lat,
|
||||
lng,
|
||||
);
|
||||
// 只有移动超过 1.5 米才画线,否则认为是原地漂移
|
||||
if (distance > 1.5) {
|
||||
_trajectoryPoints.add(newPos);
|
||||
// 性能优化:只保留最近 1000 个点
|
||||
if (_trajectoryPoints.length > 1000) _trajectoryPoints.removeAt(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 自动移动地图中心
|
||||
if (_mapController != null && _currentPosition != null) {
|
||||
_mapController?.move(_currentPosition!, 17);
|
||||
// 2. 地图跟随:如果用户在手动拖地图,就不要强制跳回中心
|
||||
if (!_isUserDragging && _mapController != null) {
|
||||
_mapController?.move(newPos, _mapController!.camera.zoom);
|
||||
}
|
||||
} else {
|
||||
debugPrint('⚠️ [FloatBarWidget] 位置数据无效: lat=$lat, lng=$lng');
|
||||
}
|
||||
}
|
||||
|
||||
/// 模拟轨迹数据(用于测试)
|
||||
/// 简化为三个固定点,飞机在这三个点之间缓慢移动
|
||||
void _startSimulationTrajectory() {
|
||||
debugPrint('🎯 [FloatBarWidget] 启动模拟轨迹数据(三点模式)');
|
||||
/// 计算两点之间的距离(米)- Haversine 公式
|
||||
double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
const double earthRadius = 6371000; // 地球半径(米)
|
||||
|
||||
final dLat = _toRadians(lat2 - lat1);
|
||||
final dLon = _toRadians(lon2 - lon1);
|
||||
|
||||
final a = sin(dLat / 2) * sin(dLat / 2) +
|
||||
cos(_toRadians(lat1)) * cos(_toRadians(lat2)) *
|
||||
sin(dLon / 2) * sin(dLon / 2);
|
||||
|
||||
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||
|
||||
return earthRadius * c;
|
||||
}
|
||||
|
||||
double _toRadians(double degrees) {
|
||||
return degrees * pi / 180;
|
||||
}
|
||||
|
||||
/// 启动智能模拟轨迹(支持多种路径模式)
|
||||
void _startSimulationTrajectory({
|
||||
SimulationMode mode = SimulationMode.circle,
|
||||
int pointCount = 20,
|
||||
}) {
|
||||
debugPrint('🎯 [FloatBarWidget] ========== 开始启动模拟轨迹 ==========');
|
||||
debugPrint(' 模式: $mode');
|
||||
debugPrint(' 点数: $pointCount');
|
||||
debugPrint(' mounted: $mounted');
|
||||
|
||||
// 如果已经有定时器在运行,先取消
|
||||
if (_simulationTimer != null && _simulationTimer!.isActive) {
|
||||
debugPrint('⚠️ [FloatBarWidget] 检测到已有定时器,先取消');
|
||||
_simulationTimer!.cancel();
|
||||
}
|
||||
|
||||
// 三个点间距拉大,形成明显的三角形路径
|
||||
final waypoints = [
|
||||
const LatLng(39.9042, 116.4074), // 起点
|
||||
const LatLng(39.9060, 116.4110), // 东北方向
|
||||
const LatLng(39.9020, 116.4120), // 东南方向
|
||||
];
|
||||
// 生成路径点集
|
||||
final waypoints = _generateWaypoints(mode, pointCount);
|
||||
debugPrint(' 生成的路径点数: ${waypoints.length}');
|
||||
|
||||
if (waypoints.isEmpty) {
|
||||
debugPrint('❌ [FloatBarWidget] 路径点生成失败');
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint(' 第一个点: ${waypoints[0]}');
|
||||
|
||||
int currentWaypoint = 0;
|
||||
int nextWaypoint = 1;
|
||||
int currentIndex = 0;
|
||||
double progress = 0;
|
||||
const double speed = 0.02; // 每步移动2%
|
||||
const double speed = 0.05; // 每步移动5%,更快看到效果
|
||||
|
||||
// 初始化位置
|
||||
debugPrint('📍 [FloatBarWidget] 设置初始位置...');
|
||||
setState(() {
|
||||
_currentPosition = waypoints[0];
|
||||
_currentHeading = 90;
|
||||
_trajectoryPoints = [waypoints[0]];
|
||||
_currentPosition = waypoints[0]; // 从起点开始
|
||||
_trajectoryPoints = [waypoints[0]]; // 轨迹从起点开始
|
||||
debugPrint(' ✅ 起始位置: $_currentPosition');
|
||||
debugPrint(' ✅ 轨迹点数: ${_trajectoryPoints.length}');
|
||||
});
|
||||
|
||||
// 使用定时器模拟缓慢移动(500ms间隔,肉眼可见移动)
|
||||
_simulationTimer = Timer.periodic(const Duration(milliseconds: 500), (
|
||||
timer,
|
||||
) {
|
||||
// 首次定位到起始区域
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_mapController != null) {
|
||||
debugPrint('🗺️ [FloatBarWidget] 移动地图到起始点...');
|
||||
_mapController!.move(waypoints[0], 16);
|
||||
debugPrint(' ✅ 地图已移动');
|
||||
} else {
|
||||
debugPrint('⚠️ [FloatBarWidget] 地图控制器为空,无法移动');
|
||||
}
|
||||
});
|
||||
|
||||
debugPrint('⏱️ [FloatBarWidget] 启动定时器(300ms间隔)...');
|
||||
debugPrint(' 📊 当前 mounted: $mounted');
|
||||
debugPrint(' 📊 当前 _currentPosition: $_currentPosition');
|
||||
|
||||
// 使用定时器模拟飞行(300ms间隔,流畅移动)
|
||||
_simulationTimer = Timer.periodic(const Duration(milliseconds: 300), (timer) {
|
||||
debugPrint('🔥 [定时器触发] mounted=$mounted, currentIndex=$currentIndex, progress=${progress.toStringAsFixed(2)}');
|
||||
|
||||
if (!mounted) {
|
||||
debugPrint('⚠️ [FloatBarWidget] Widget 未挂载,取消定时器');
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 首次触发时定位地图到起始区域
|
||||
if (progress == speed && _mapController != null) {
|
||||
try {
|
||||
_mapController!.move(waypoints[0], 16);
|
||||
} catch (_) {}
|
||||
}
|
||||
final start = waypoints[currentIndex];
|
||||
final nextIndex = (currentIndex + 1) % waypoints.length;
|
||||
final end = waypoints[nextIndex];
|
||||
|
||||
// 计算当前点到下一个点的移动
|
||||
final start = waypoints[currentWaypoint];
|
||||
final end = waypoints[nextWaypoint];
|
||||
|
||||
// 线性插值
|
||||
// 线性插值 - 计算轨迹的"前端"位置
|
||||
progress += speed;
|
||||
if (progress >= 1.0) {
|
||||
progress = 0;
|
||||
currentWaypoint = nextWaypoint;
|
||||
nextWaypoint = (currentWaypoint + 1) % waypoints.length;
|
||||
currentIndex = nextIndex;
|
||||
|
||||
// 到达起点时继续循环(不要停!)
|
||||
if (currentIndex == 0) {
|
||||
debugPrint('✅ [FloatBarWidget] 🎉 完成一圈,继续飞行...');
|
||||
// timer.cancel(); // 不要停止,继续飞
|
||||
// return; // 不要返回
|
||||
}
|
||||
}
|
||||
|
||||
// 计算当前位置
|
||||
final lat = start.latitude + (end.latitude - start.latitude) * progress;
|
||||
final lng =
|
||||
start.longitude + (end.longitude - start.longitude) * progress;
|
||||
// 计算轨迹前端位置(水流到哪里了)
|
||||
final trailLat = start.latitude + (end.latitude - start.latitude) * progress;
|
||||
final trailLng = start.longitude + (end.longitude - start.longitude) * progress;
|
||||
final trailPosition = LatLng(trailLat, trailLng);
|
||||
|
||||
// 计算航向角
|
||||
final dx = end.longitude - start.longitude;
|
||||
final dy = end.latitude - start.latitude;
|
||||
final heading = atan2(dy, dx) * 180 / pi;
|
||||
// 核心改进:计算飞机的航向角(让头朝向前进方向)
|
||||
final angle = atan2(end.longitude - start.longitude, end.latitude - start.latitude) * 180 / pi;
|
||||
|
||||
setState(() {
|
||||
_currentPosition = LatLng(lat, lng);
|
||||
_currentHeading = heading;
|
||||
// 1. 刷新悬浮条自身(如果需要)
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentPosition = trailPosition;
|
||||
_currentHeading = angle; // 赋予飞机灵魂
|
||||
|
||||
// 轨迹产生(拉烟)
|
||||
_trajectoryPoints.add(trailPosition);
|
||||
if (_trajectoryPoints.length > 800) _trajectoryPoints.removeAt(0);
|
||||
|
||||
debugPrint(' 🔄 setState更新: 飞机=$_currentPosition, 航向=${angle.toStringAsFixed(1)}°, 轨迹点=${_trajectoryPoints.length}');
|
||||
});
|
||||
}
|
||||
|
||||
// 添加轨迹点
|
||||
if (_currentPosition != null) {
|
||||
_trajectoryPoints.add(_currentPosition!);
|
||||
if (_trajectoryPoints.length > 500) {
|
||||
_trajectoryPoints.removeAt(0);
|
||||
}
|
||||
}
|
||||
// 2. 🔥 核心:刷新已经打开的弹窗内部
|
||||
_bottomSheetSetState?.call(() {
|
||||
// 弹窗内部组件会重新执行 build,读取到最新的 _currentPosition 和 _currentHeading
|
||||
debugPrint(' 📱 弹窗已刷新');
|
||||
});
|
||||
|
||||
// 移动地图中心到无人机当前位置
|
||||
if (_currentPosition != null) {
|
||||
_mapController?.move(_currentPosition!, 16);
|
||||
// 3. 移动地图中心
|
||||
if (!_isUserDragging) {
|
||||
_mapController?.move(trailPosition, 16);
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'📍 模拟位置: lat=${lat.toStringAsFixed(6)}, lng=${lng.toStringAsFixed(6)}, heading=${heading.toStringAsFixed(1)}, progress=${progress.toStringAsFixed(2)}',
|
||||
);
|
||||
if (progress < 0.1 || progress > 0.9) {
|
||||
debugPrint(
|
||||
'✈️ 飞机飞行: 进度=${(progress * 100).toStringAsFixed(0)}%, '
|
||||
'轨迹点=${_trajectoryPoints.length}, '
|
||||
'位置=(${trailLat.toStringAsFixed(4)}, ${trailLng.toStringAsFixed(4)})',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
debugPrint(' 模拟定时器已启动,三点循环模式(500ms间隔)');
|
||||
debugPrint(' ✅ 模拟定时器已启动');
|
||||
debugPrint(' ✈️ 飞机将沿路径飞行(像拉烟画画)');
|
||||
debugPrint(' 🛤️ 轨迹将从飞机尾部产生');
|
||||
debugPrint('🎯 [FloatBarWidget] ========== 模拟轨迹启动完成 ==========');
|
||||
}
|
||||
|
||||
/// 生成不同类型的路径点集
|
||||
List<LatLng> _generateWaypoints(SimulationMode mode, int count) {
|
||||
const centerLat = 39.9042;
|
||||
const centerLng = 116.4074;
|
||||
|
||||
switch (mode) {
|
||||
case SimulationMode.circle:
|
||||
return _generateCirclePath(centerLat, centerLng, count);
|
||||
case SimulationMode.polygon:
|
||||
return _generatePolygonPath(centerLat, centerLng, count);
|
||||
case SimulationMode.bow:
|
||||
return _generateBowPath(centerLat, centerLng, count);
|
||||
case SimulationMode.rectangle:
|
||||
return _generateRectanglePath(centerLat, centerLng, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成圆形路径
|
||||
List<LatLng> _generateCirclePath(double centerLat, double centerLng, int points) {
|
||||
final List<LatLng> path = [];
|
||||
const double radius = 0.002; // 约200米半径
|
||||
const double angleStep = (2 * pi) / 30; // 30个点形成圆
|
||||
|
||||
for (int i = 0; i < 30; i++) {
|
||||
final angle = i * angleStep;
|
||||
final lat = centerLat + radius * cos(angle);
|
||||
final lng = centerLng + radius * sin(angle);
|
||||
path.add(LatLng(lat, lng));
|
||||
}
|
||||
path.add(path[0]); // 闭合圆形
|
||||
|
||||
debugPrint('🔵 生成圆形路径: ${path.length}个点');
|
||||
return path;
|
||||
}
|
||||
|
||||
/// 生成多边形路径(六边形)
|
||||
List<LatLng> _generatePolygonPath(double centerLat, double centerLng, int points) {
|
||||
final List<LatLng> path = [];
|
||||
const double radius = 0.0025; // 约250米
|
||||
const int sides = 6; // 六边形
|
||||
const double angleStep = (2 * pi) / sides;
|
||||
|
||||
for (int i = 0; i <= sides; i++) {
|
||||
final angle = i * angleStep - pi / 2; // 从顶部开始
|
||||
final lat = centerLat + radius * cos(angle);
|
||||
final lng = centerLng + radius * sin(angle);
|
||||
path.add(LatLng(lat, lng));
|
||||
}
|
||||
|
||||
debugPrint('⬡ 生成六边形路径: ${path.length}个点');
|
||||
return path;
|
||||
}
|
||||
|
||||
/// 生成弓字形路径(Zigzag)
|
||||
List<LatLng> _generateBowPath(double centerLat, double centerLng, int points) {
|
||||
final List<LatLng> path = [];
|
||||
const double width = 0.004; // 宽度约400米
|
||||
const double height = 0.003; // 高度约300米
|
||||
const int rows = 5; // 5行
|
||||
|
||||
for (int row = 0; row <= rows; row++) {
|
||||
final lat = centerLat - height / 2 + (height / rows) * row;
|
||||
|
||||
// 偶数行从左到右,奇数行从右到左
|
||||
if (row % 2 == 0) {
|
||||
path.add(LatLng(lat, centerLng - width / 2));
|
||||
path.add(LatLng(lat, centerLng + width / 2));
|
||||
} else {
|
||||
path.add(LatLng(lat, centerLng + width / 2));
|
||||
path.add(LatLng(lat, centerLng - width / 2));
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('〰️ 生成弓字形路径: ${path.length}个点, $rows行');
|
||||
return path;
|
||||
}
|
||||
|
||||
/// 生成矩形路径
|
||||
List<LatLng> _generateRectanglePath(double centerLat, double centerLng, int points) {
|
||||
final List<LatLng> path = [];
|
||||
const double width = 0.004; // 宽约400米
|
||||
const double height = 0.003; // 高约300米
|
||||
|
||||
// 四个角点(顺时针)
|
||||
path.add(LatLng(centerLat + height / 2, centerLng - width / 2)); // 左上
|
||||
path.add(LatLng(centerLat + height / 2, centerLng + width / 2)); // 右上
|
||||
path.add(LatLng(centerLat - height / 2, centerLng + width / 2)); // 右下
|
||||
path.add(LatLng(centerLat - height / 2, centerLng - width / 2)); // 左下
|
||||
path.add(path[0]); // 闭合
|
||||
|
||||
debugPrint('▭ 生成矩形路径: ${path.length}个点');
|
||||
return path;
|
||||
}
|
||||
|
||||
/// 加载视频流
|
||||
@@ -492,8 +693,13 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.85,
|
||||
),
|
||||
builder: (ctx) => _buildBottomSheetContent(status),
|
||||
);
|
||||
builder: (ctx) => StatefulBuilder( // 🔥 关键:使用 StatefulBuilder
|
||||
builder: (context, setSheetState) {
|
||||
_bottomSheetSetState = setSheetState; // 捕获弹窗内部的刷新函数
|
||||
return _buildBottomSheetContent(status);
|
||||
},
|
||||
),
|
||||
).then((_) => _bottomSheetSetState = null); // 弹窗关闭时置空,防止内存泄漏
|
||||
}
|
||||
|
||||
/// 构建底部弹窗内容
|
||||
@@ -1308,33 +1514,18 @@ class FloatBarWidgetState extends State<FloatBarWidget> {
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
Marker(
|
||||
key: const ValueKey('drone_current_marker'),
|
||||
key: const ValueKey('drone_marker'), // 加上 Key 确保状态刷新
|
||||
point: _currentPosition!,
|
||||
width: 60,
|
||||
height: 60,
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Transform.rotate(
|
||||
angle: (_currentHeading ?? 0) * pi / 180,
|
||||
// 注意:Icons.flight 默认头朝上(0度),如果你的图标偏了,在这里加个 pi/2 之类的偏移
|
||||
angle: (_currentHeading ?? 0) * pi / 180,
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(2, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Icon(
|
||||
Icons.flight,
|
||||
color: Color(0xFF165DFF),
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.flight,
|
||||
color: Color(0xFF165DFF),
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user