新增完全离线的地图功能

This commit is contained in:
2025-10-13 18:36:28 +08:00
parent 35a90f1635
commit 0d6f4d4f71
5451 changed files with 346 additions and 18 deletions
@@ -1,7 +1,10 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/model/business/kr_outbound_item.dart';
import 'package:latlong2/latlong.dart';
import '../../../utils/kr_fm_tc.dart';
import '../controllers/kr_home_controller.dart';
@@ -15,13 +18,17 @@ class KRHomeMapView extends GetView<KRHomeController> {
@override
Widget build(BuildContext context) {
// 初始化地图缓存
// KRFMTC.kr_initMapCache();
return Obx(() => FlutterMap(
//KRFMTC.kr_initMapCache();
return GetBuilder<KRHomeController>(
id: 'map_markers', // ① 指定唯一ID
builder: (controller) {
return FlutterMap(
mapController: controller.kr_mapController,
options: MapOptions(
initialCenter: _kr_getInitialMapCenter(),
initialZoom: 4.0,
initialZoom: 2.0,
minZoom: 2,
maxZoom: 5,
initialRotation: 0,
backgroundColor: Theme.of(context).primaryColor,
onMapEvent: (event) {
@@ -29,7 +36,10 @@ class KRHomeMapView extends GetView<KRHomeController> {
if (event is MapEventMoveEnd) {
if (event.source == MapEventSource.dragEnd ||
event.source == MapEventSource.multiFingerEnd) {
controller.kr_isUserMoving.value = true;
controller.showMarkersMap();//刷新地图marker
}
}
} catch (e) {
@@ -44,30 +54,155 @@ class KRHomeMapView extends GetView<KRHomeController> {
),
children: [
_kr_buildTileLayer(context),
MarkerLayer(
markers: controller.kr_subscribeService.allList
.map((item) => _buildStyledMarker(item))
.toList(),
),
_kr_buildMarkers(context),
],
));
}
);
},
);
}
/// 构建地图瓦片层
Widget _kr_buildTileLayer(BuildContext context) {
return TileLayer(
tileProvider: AssetTileProvider(),
urlTemplate: KRFMTC.kr_getTileUrl(),
subdomains: KRFMTC.kr_getTileSubdomains(),
userAgentPackageName: 'app.brAccelerator.com',
tileProvider: KRFMTC.kr_getTileProvider(),
userAgentPackageName: 'app.baertw123.com',//app.brAccelerator.com
/* tileProvider: KRFMTC.kr_getTileProvider(),*/
tileBuilder: (context, child, tileImage) {
return child;
},
);
}
MarkerLayer _kr_buildMarkers(BuildContext content){
int zoom = 2;
try{
zoom = controller.kr_mapController.camera.zoom.toInt();
}catch(e){
e.printError();
}
print("当前层级:$zoom ");
if(zoom<5){//国家
return MarkerLayer(
markers: controller.kr_subscribeService.countryOutboundList
.map((item) => _buildStyledProvMarker(item))
.toList(),
);
}else{
return MarkerLayer(
markers: controller.kr_subscribeService.allList
.map((item) => _buildStyledMarker(item))
.toList(),
);
}
}
Marker _buildStyledProvMarker(dynamic node) {
print("省级:${node.country} #####${node.outboundList}");
double z = controller.kr_mapController.camera.zoom;
double lat = 0.0;
double lng = 0.0;
String id = "";
int len = -1;
if(node.outboundList.isNotEmpty){
KROutboundItem f = node.outboundList.first as KROutboundItem;
len = node.outboundList.length;
id = f.id;
lat = f.latitudeCountry;
lng = f.longitudeCountry;
print("${f.country} -lat坐标 ${lat},点坐标${f.latitude} lng坐标 ${lng},点坐标${f.longitude}");
}
double fontSize = 8;
double radius = 0+len*10 + z*10;
double radius2 = 20;
if(z<3){
fontSize = 8;
radius = 0+len*10 + z*10;
radius2 = 15;
}else if(z<4){
fontSize = 8;
radius = 30+len*10 + z*10;
radius2 = 20;
}else if(z<5){
fontSize = 10;
radius = 70+len*10 + z*10;
radius2 = 20;
}else if(z<6){
fontSize = 12;
radius = 100+len*10+ z*10;
radius2 = 30;
}
return Marker(
key: ValueKey('key_${id}'),
point: LatLng(lat, lng),
width: radius,
height: radius,
child: _buildDoubleCircle(radius,radius2, Colors.blue, len,fontSize),
);
}
/// 构建样式化的标记
Marker _buildStyledMarker(dynamic node) {
print("原始Marker${node}");
int type = 0;
MaterialColor markerColor = Colors.grey;
//如果没订阅过,就是灰色
bool status = controller.kr_isConnected.value;
print("连接状态:$status");
if(status){
if (node.urlTestDelay.value == 0) {
// 延迟为0时使用默认颜色
type=0;
markerColor = Colors.blue;
} else if (node.urlTestDelay.value < 500) {
// 延迟小于500ms显示绿色
type=1;
markerColor = Colors.green;
} else if (node.urlTestDelay.value < 3000) {
// 延迟小于3000ms显示黄色
type=2;
markerColor = Colors.yellow;
} else if (node.urlTestDelay.value > 3000 ){
// 超时显示红色
type=3;
markerColor = Colors.red;
}
}else{
type=4;
markerColor = Colors.blue;
}
if(node.selected==1){
if(type==1||type==0){//只有绿色才有雷达效果
return RadarMarker(
key: ValueKey('key_${node.id}'),
point: LatLng(node.latitude, node.longitude),
radarSize1:75,
radarSize2:37,
radarSize3: 25.0,
innerColor: Colors.green,
outerColor: Colors.green,
animationDuration: const Duration(seconds: 4),
).toMarker();
}else{ //蓝色. 灰色 黄色
return Marker(
key: ValueKey('key_${node.id}'),
point: LatLng(node.latitude, node.longitude),
width: 150,
height: 150.0,
child: _buildDoubleCircle(150,20, markerColor, -1,14),
);
}
}else{
return Marker(
key: ValueKey('key_${node.id}'),
point: LatLng(node.latitude, node.longitude),
width: 30.0,
height: 30.0,
child: _buildDoubleCircle(30,10, markerColor, -1,14),
);
}
/*
return Marker(
point: LatLng(node.latitude, node.longitude),
width: 42.w,
@@ -99,7 +234,7 @@ class KRHomeMapView extends GetView<KRHomeController> {
);
},
),
);
);*/
}
/// 获取初始地图中心点
@@ -109,18 +244,209 @@ class KRHomeMapView extends GetView<KRHomeController> {
}
if (controller.kr_cutSeletedTag.isEmpty) {
return const LatLng(35.0, 105.0); // 修改默认位置为中国中部
return const LatLng(5.0, 105.0); // 修改默认位置为中国中部
}
final selectedNode = controller.kr_subscribeService.allList
.firstWhereOrNull((item) => item.tag == controller.kr_cutSeletedTag.value);
if (selectedNode == null) {
return const LatLng(35.0, 105.0); // 修改默认位置为中国中部
return const LatLng(5.0, 105.0); // 修改默认位置为中国中部35
}
//更新当前数据为选中
// controller.kr_subscribeService.allList[index].selected = 1;
// controller.selectMarkersMap(index);
// 更新最后的地图中心点
controller.kr_lastMapCenter.value =
LatLng(selectedNode.latitude, selectedNode.longitude);
return controller.kr_lastMapCenter.value;
}
}
}
// 构建双层圆形标记
Widget _buildDoubleCircle(double circleW1,double circleW2,MaterialColor markerColor, int num,double fontSize) {
return Stack(
alignment: Alignment.center,
children: [
// 外圈(淡蓝色,50%透明度)
Container(
width: circleW1,
height: circleW1,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:markerColor.withOpacity(0.2), // 淡蓝色,50%透明度
),
),
// 内圈(蓝色,0%透明度)
Container(
width: circleW2,
height: circleW2,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: markerColor, // 蓝色,0%透明度(完全不透明)
),
),
// 中心数字文本
Text(
(num==-1)?' ':'$num',
style: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: Colors.white, // 白色文字
),
),
],
);
}
class RadarMarker extends StatefulWidget {
final LatLng point;
final double radarSize1;
final double radarSize2;
final double radarSize3;
final Color innerColor;
final Color outerColor;
final Duration animationDuration;
const RadarMarker({
super.key,
required this.point,
this.radarSize1 = 120,
this.radarSize2 = 60,
this.radarSize3 = 40,
this.innerColor = Colors.blue,
this.outerColor = Colors.blue,
//this.animationDuration = const Duration(seconds: 1),
this.animationDuration = const Duration(milliseconds: 500),
});
@override
State<RadarMarker> createState() => _RadarMarkerState();
}
class _RadarMarkerState extends State<RadarMarker>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.animationDuration,
vsync: this,
)..repeat();
_scaleAnimation = Tween<double>(begin: 1.0, end: 2.5).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Stack(
alignment: Alignment.center,
children: [
// 第一层外圈雷达波纹
Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Container(
width: widget.radarSize1,
height: widget.radarSize1,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.outerColor.withOpacity(0.2),
border: Border.all(
color: widget.outerColor.withOpacity(0.2),
width: 2.0,
),
),
),
),
),
// 第二层波纹
Transform.scale(
scale: _scaleAnimation.value * 0.7,
child: Opacity(
opacity: _opacityAnimation.value * 0.7,
child: Container(
width: widget.radarSize2,
height: widget.radarSize2,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.outerColor.withOpacity(0.3),
border: Border.all(
color: widget.outerColor.withOpacity(0.3),
width: 1.5,
),
),
),
),
),
// 中心圆形
Container(
width: widget.radarSize3,
height: widget.radarSize3,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.innerColor,
boxShadow: [
BoxShadow(
color: widget.innerColor.withOpacity(0.5),
blurRadius: 10.0,
spreadRadius: 2.0,
),
],
border: Border.all(
color: Colors.white,
width: 2.0,
),
),
),
],
);
},
);
}
}
extension RadarMarkerExtension on RadarMarker {
Marker toMarker() {
return Marker(
point: point,
width: radarSize1,
height: radarSize1,
child: this,
);
}
}
class AssetTileProvider extends TileProvider {
@override
ImageProvider getImage(TileCoordinates coords, TileLayer options) {
// 构建assets路径:z/x/y.png
final path = 'assets/map_tiles/${coords.z}/${coords.x}/${coords.y}.png';
return AssetImage(path);
}
}