Files
UnionApp/lib/pages/map_page.dart
spasolreisa 9ce601aa8d initial
2026-04-16 14:26:52 +08:00

248 lines
7.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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),
),
),
],
),
);
}
}