This commit is contained in:
spasolreisa
2026-04-16 14:26:52 +08:00
commit 9ce601aa8d
151 changed files with 11467 additions and 0 deletions

248
lib/pages/map_page.dart Normal file
View File

@@ -0,0 +1,248 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:geolocator/geolocator.dart';
import 'package:geocoding/geocoding.dart';
class MapPage extends StatefulWidget {
const MapPage({super.key});
@override
State<MapPage> createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
// 默认中心点(北京)
LatLng _currentPosition = const LatLng(39.9042, 116.4074);
String _address = "正在加载地址...";
bool _isLoading = false;
// 地图控制器
final MapController _mapController = MapController();
@override
void initState() {
super.initState();
_getCurrentLocation();
}
// 1. 获取当前位置并解析地址
Future<void> _getCurrentLocation() async {
setState(() {
_isLoading = true;
_address = "正在检查权限...";
});
try {
// 1. 检查服务是否开启
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
setState(() {
_address = "请在系统设置中开启‘定位服务’总开关";
_isLoading = false;
});
return;
}
// 2. 检查权限
LocationPermission permission = await Geolocator.checkPermission();
// 如果权限被拒绝,尝试请求
if (permission == LocationPermission.denied) {
setState(() {
_address = "正在请求权限...";
});
// 🔑 注意macOS 上这行可能不会弹窗,而是直接返回 denied
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
setState(() {
_address = "权限被拒绝。请去【系统设置->隐私->定位】中手动开启本App权限。";
_isLoading = false;
});
return;
}
}
if (permission == LocationPermission.deniedForever) {
setState(() {
_address = "权限被永久拒绝,请在系统设置中手动开启。";
_isLoading = false;
});
return;
}
// 3. 获取位置
setState(() {
_address = "正在获取坐标...";
});
// 🔑 添加超时限制,防止卡死
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: const Duration(seconds: 15),
).timeout(
const Duration(seconds: 15),
onTimeout: () {
throw Exception("定位超时,请检查网络连接或移动到有信号的地方");
},
);
LatLng newPos = LatLng(position.latitude, position.longitude);
_mapController.move(newPos, 15.0);
setState(() {
_currentPosition = newPos;
});
await _getAddressFromLatLng(newPos);
} catch (e) {
debugPrint("定位错误详情: $e");
setState(() {
_address = "定位失败: ${e.toString()}";
_isLoading = false;
});
}
}
// 2. 逆地理编码:经纬度 -> 文字地址
Future<void> _getAddressFromLatLng(LatLng position) async {
try {
List<Placemark> placemarks = await placemarkFromCoordinates(
position.latitude,
position.longitude,
);
if (placemarks.isNotEmpty) {
Placemark place = placemarks[0];
setState(() {
// 组合地址:省 + 市 + 区 + 街道
_address = "${place.street ?? ""}, ${place.locality ?? ""}, ${place.administrativeArea ?? ""}";
});
}
} catch (e) {
setState(() {
_address = "地址解析失败";
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent, // 保持透明,透出玻璃效果
body: Stack(
children: [
// --- 底层:地图 ---
// --- 底层:地图 ---
FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter: _currentPosition,
initialZoom: 13.0,
// 🔑 添加交互标志,确保地图能响应鼠标拖拽
interactionOptions: const InteractionOptions(
flags: InteractiveFlag.all,
),
),
children: [
TileLayer(
// ✅ 替换为 CartoDB 浅色主题速度快无需Key
urlTemplate: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
subdomains: ['a', 'b', 'c', 'd'],
userAgentPackageName: 'com.example.app',
// 🔑 添加错误监听,看看是不是还有网络问题
errorTileCallback: (tile, error, stackTrace) {
debugPrint('地图瓦片加载错误: $error');
},
),
MarkerLayer(
markers: [
Marker(
width: 80.0,
height: 80.0,
point: _currentPosition,
child: const Icon(
Icons.location_pin,
color: Colors.red,
size: 40,
),
),
],
),
],
),
// --- 顶层UI 控件 ---
// 1. 顶部地址卡片 (玻璃风格)
Positioned(
top: 50,
left: 20,
right: 20,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.85), // 半透明白
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Row(
children: [
const Icon(Icons.place, color: Colors.blueAccent),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"当前位置",
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 4),
Text(
_address,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
if (_isLoading)
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
),
),
// 2. 右下角定位按钮
Positioned(
bottom: 100, // 留出底部导航栏的空间
right: 20,
child: FloatingActionButton(
onPressed: _getCurrentLocation,
backgroundColor: Colors.white,
child: const Icon(Icons.my_location, color: Colors.blue),
),
),
],
),
);
}
}