效果图

代码
Dart
import 'dart:io';
import 'package:amap_map/amap_map.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:map_launcher/map_launcher.dart' as map_launcher;
import 'package:permission_handler/permission_handler.dart';
import 'package:x_amap_base/x_amap_base.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:mate/bluetooth/headset.dart';
import 'package:mate/caches/user_data.dart';
import 'package:mate/manager/device_manager.dart';
import 'dart:math';
import '../config.dart';
import '../generated/l10n.dart';
class AMapPage extends StatefulWidget {
const AMapPage({super.key,required this.headset});
final Headset headset;
@override
State<StatefulWidget> createState() => _AMapPage();
}
class _AMapPage extends State<AMapPage> {
AMapController? mapController; // 地图控制器
LatLng? latLng; // 当前坐标
final earphoneMarkers = <Marker>[];
bool _isMapReady = false; // 地图是否准备就绪
bool _isLoading = true; // 是否正在加载
bool _showMyLocation = false;// 控制是否显示定位蓝点
bool isFinding = false;//是否正在查找
String address = ""; //存储定位地址
String time = UserData.instance.getUserTime();//获取时间戳
// 获取保存的耳机的经纬度
double earphoneLatitude = UserData.instance.getEarphoneLatitude();
double earphoneLongitude = UserData.instance.getEarphoneLongitude();
final player = AudioPlayer(); //音频播放器
//存储当前定位
double myLatitude = 0.0;
double myLongitude = 0.0;
//存储耳机和我之间的距离
int meter = 0;
@override
void initState() {
super.initState();
AMapInitializer.updatePrivacyAgree(AMapPrivacyStatement(hasContains: true,hasShow: true,hasAgree: true)); //初始化隐私协议
_initializeLocation();
}
@override
void dispose() {
super.dispose();
player.stop();
player.dispose();
}
//==================================初始化位置==================================
Future<void> _initializeLocation() async {
setState(() => _isLoading = true);//显示加载中
//获取我的位置
getMyLocation();
//读取耳机位置
if (earphoneLatitude != 0 && earphoneLongitude != 0) {
latLng = LatLng(earphoneLatitude, earphoneLongitude);
print('【使用缓存位置】从UserData读取耳机的位置: (${latLng!.latitude}, ${latLng!.longitude})');
//获取耳机的位置字符串
_getAddressFromLatLng(earphoneLatitude,earphoneLongitude);
print("获取耳机的区域位置");
} else {
print('【缓存位置】无缓存位置数据 (latitude=$earphoneLatitude, longitude=$earphoneLongitude)');
}
setState(() => _isLoading = false); //隐藏加载中
}
//=========================获取我的位置=========================
Future<void> getMyLocation() async {
// 检查定位服务
bool service = await Geolocator.isLocationServiceEnabled();
if (!service) {
print("定位服务未开启");
return;
}
// 检查权限
final permission = await Permission.locationWhenInUse.status;
if (permission != PermissionStatus.granted) {
final status = await Permission.locationWhenInUse.request();
if (status != PermissionStatus.granted) {
print("没有定位权限");
return;
}
}
try {
// 获取当前位置
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
print("获取到我的位置为: ${position.latitude}, ${position.longitude}");
setState(() {
myLatitude = position.latitude;
myLongitude = position.longitude;
});
//获取我和耳机之间的距离
double distance = Geolocator.distanceBetween(myLatitude, myLongitude, earphoneLatitude, earphoneLongitude);
setState(() {
meter = distance.round(); // 四舍五入取整
});
// 显示我的位置蓝点
if (!_showMyLocation) {
setState(() => _showMyLocation = true);
}
print(" 我和耳机的距离: ${meter}米");
print(" 我的位置: ($myLatitude, $myLongitude)");
print(" 耳机位置: (${earphoneLatitude}, ${earphoneLongitude})");
} catch (error) {
print("上传位置失败: $error");
}
}
//==================================更新距离==================================
void _updateDistance() {
if (myLatitude != 0 && myLongitude != 0 && myLatitude != 0 && myLongitude != 0) {
//获取我和耳机之间的距离
double distance = Geolocator.distanceBetween(myLatitude, myLongitude, earphoneLatitude, earphoneLongitude);
setState(() {
meter = distance.round();
});
print("📏 距离更新: ${meter}米");
}
}
//==================================创建耳机标记================================
Future<void> _createEarphoneMarker() async {
print('🔴【创建耳机标记】开始');
if (earphoneLatitude == 0 || earphoneLongitude == 0) {
print(' ❌ 耳机位置无效');
return;
}
final icon = await BitmapDescriptor.fromAssetImage(const ImageConfiguration(devicePixelRatio: 1), "assets/devices/device_icon_small.png");
final marker = Marker(position: LatLng(earphoneLatitude, earphoneLongitude), icon: icon);
earphoneMarkers.add(marker);
print(' ✅ 耳机标记创建成功,当前标记数量: ${earphoneMarkers.length}');
}
//============================== 将经纬度转换为地址==============================
Future<void> _getAddressFromLatLng(double latitude, double longitude) async {
try {
List<Placemark> placemarks = await placemarkFromCoordinates(latitude, longitude);
if (placemarks.isNotEmpty) {
Placemark place = placemarks[0];
// 构建完整地址字符串
setState(() {
address = [place.country! + place.street!,].where((s) => s != null && s.isNotEmpty).join(', ');
});
print("当前位置: $address");
}
} catch (e) {
print("获取地址失败: $e");
}
}
//=============================移动地图到指定位置===============================
void _moveToPosition(LatLng position, {double zoom = 15}) {
if (!_isMapReady || mapController == null) {
print(' ⚠️【移动取消】地图未就绪');
return;
}
print('🎯 移动地图到: (${position.latitude}, ${position.longitude})');
mapController!.moveCamera(
CameraUpdate.newCameraPosition(
CameraPosition(target: position, zoom: zoom),
),
);
print(' ✅【移动成功】相机已移动');
}
//=========================地图创建回调优化==================================
void onMapCreate(AMapController controller) {
mapController = controller;
_isMapReady = true;
print(' 地图控制器已保存,_isMapReady=true');
// 延迟初始化设备,确保地图已就绪
print(' 延迟100ms后初始化设备');
Future.delayed(const Duration(milliseconds: 100), () {
if (_isMapReady && mapController != null && earphoneLatitude != 0 && earphoneLongitude != 0) {
print(' 移动到耳机位置: ($earphoneLatitude, $earphoneLongitude)');
_moveToPosition(LatLng(earphoneLatitude, earphoneLongitude));
}
initDevice();
});
}
//==================================设备初始化==================================
void initDevice() async {
//获取设备位置
latLng = LatLng(earphoneLatitude, earphoneLongitude);
// 如果没有设备位置,尝试显示当前位置
if (latLng == null) {
print('没有设备位置,请求当前位置');
requestLocation();
} else if (_isMapReady) {
print('地图已就绪,移动到设备位置');
_moveToPosition(latLng!);
}
// 添加所有设备标记
print('开始添加设备标记');
await _createEarphoneMarker();
if (mounted) setState(() {});
}
//==================================位置变化回调==================================
void onLocationChanged(AMapLocation location) async {
// 从 location 对象中获取经纬度
if (location.latLng == null) {
print('❌ 位置数据为空');
return;
}
//获取我的位置
double lat = location.latLng!.latitude;
double lng = location.latLng!.longitude;
print('📍 当前位置: ($lat, $lng)');
// 更新我的位置
setState(() {
myLatitude = lat;
myLongitude = lng;
});
// 更新距离
_updateDistance();
//当前我的位置
final currentPos = LatLng(myLatitude, myLongitude);
print('当前位置: (${currentPos.latitude}, ${currentPos.longitude})');
// 如果是首次获取位置,移动到当前位置
if (latLng == null && _isMapReady) {
print(' 首次获取位置且地图已就绪,更新相机位置');
setState(() {
latLng = currentPos;
print('latLng 更新为: (${latLng!.latitude}, ${latLng!.longitude})');
});
// 延迟一下确保地图已准备好
print(' 延迟200ms后移动相机');
Future.delayed(const Duration(milliseconds: 200), () {
if (_isMapReady && mapController != null) {
print('执行相机移动');
_moveToPosition(currentPos); //锁定我的位置,实时更新
} else {
print(' 地图未就绪,无法移动: _isMapReady=$_isMapReady, mapController=${mapController != null}');
}
});
} else {
print('非首次获取位置或地图未就绪: latLng!=null? ${latLng != null}, _isMapReady=$_isMapReady');
}
}
//==================================播放声音==================================
void playSound() {
print('\n🔊🔊🔊【播放声音】用户点击播放声音按钮');
final command = DeviceManager.instance.current()?.command;
if (command == null) {
print(' ❌ 设备未连接,无法播放声音');
Fluttertoast.showToast(msg: S.current.disconnected);
return;
}
print(' ✅ 设备已连接,发送查找指令');
command.findHeadset(true);
_showFindDeviceDialog(command);
}
void _showFindDeviceDialog(dynamic command) {
print(' 💬 显示查找设备弹窗');
showDialog(
context: context,
barrierDismissible: false,
builder: (builder) {
return PopScope(
canPop: false,
child: AlertDialog(
title: Text(S.current.findDevice),
content: Text(S.current.findDeviceContext),
actions: [
TextButton(
onPressed: () {
print(' 🛑 用户点击停止查找');
command.findHeadset(false);
Navigator.pop(context);
print(' 💬 弹窗关闭');
},
child: Text(S.current.stopFind),
)
],
),
);
},
);
}
//==================================请求当前位置==================================
void requestLocation() {
setState(() {
_showMyLocation = true;
});
}
//==============================停止查找==============================
void onStop() async{
await player.stop();
setState(() {
isFinding = false;
});
}
//==============================开始查找==============================
void onStart() async {
//开始查找
await player.setReleaseMode(ReleaseMode.loop);//一直播放
await player.play(AssetSource('mp3/findphone_alert_long.mp3'));
setState(() {
isFinding = true;
});
}
@override
Widget build(BuildContext context) {
print('【构建地图】标记数量: ${earphoneMarkers.length}');
final data = MediaQuery.of(context);
final height = data.size.height;
return Scaffold(
body: _isLoading
? const Center(child: CircularProgressIndicator())
: Stack(
children: [
//==========地图==========
Container(
height: height,
width: double.infinity,
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(15),
child: AMapWidget(
onMapCreated: onMapCreate,
onLocationChanged: onLocationChanged, // 启用位置变化回调
myLocationStyleOptions: MyLocationStyleOptions(
_showMyLocation, //是否显示蓝点
circleFillColor: Colors.transparent,
circleStrokeColor: Colors.blue,
circleStrokeWidth: 0,
),
markers: earphoneMarkers.toSet(), //地图标记
initialCameraPosition: latLng == null
? const CameraPosition(
target: LatLng(39.909187, 116.397451), //北京
zoom: 10,
)
: CameraPosition(target: latLng!, zoom: 15),
compassEnabled: true, //显示指南针
scaleEnabled: true, //显示比例尺
),
),
),
// 播放声音卡片
_buildSoundCard(),
// 导航卡片
_buildNavigationCard(),
// 位置卡片
_buildLocationCard(),
],
),
);
}
//导航弹窗
void showBottomSheetDialog(BuildContext context){
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.black,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16))
),
builder: (BuildContext context){
return Container(
padding: EdgeInsets.symmetric(
vertical: 20,
//horizontal: 10
),
height: 240,
child: Column(
children: [
GestureDetector(
onTap: () async {
goGaodeMap();
},
child: Container(
width: 325,
height: 52,
decoration: BoxDecoration(
color: Color(0xFFA8A8A8),
borderRadius: BorderRadius.circular(10)
),
child: Center(
child: Text("高德地图",style: TextStyle(color:Colors.white,fontSize: 18),),
),
),
),
SizedBox(height: 5,),
GestureDetector(
onTap: (){
//跳转百度地图
goBaiduMap();
},
child: Container(
width: 325,
height: 52,
decoration: BoxDecoration(
color: Color(0xFFA8A8A8),
borderRadius: BorderRadius.circular(10)
),
child: Center(
child: Text("百度地图",style: TextStyle(color:Colors.white,fontSize: 18),),
),
),
),
SizedBox(height: 25,),
Container(
height: 1,
width: double.infinity,
color: Color(0xFFA8A8A8),
),
SizedBox(height: 25,),
GestureDetector(
onTap: (){
Navigator.pop(context);
},
child: Container(
child: Center(
child: Text(S.current.cancel,style: TextStyle(color:Colors.white,fontSize: 18),),
),
),
)
],
),
);
}
);
}
///跳转高德地图
Future<void> goGaodeMap() async{
try {
// 检查高德地图是否安装
bool isAmapAvailable = await map_launcher.MapLauncher.isMapAvailable(map_launcher.MapType.amap);
if (isAmapAvailable) {
print('高德地图已安装,准备跳转');
//耳机的位置
LatLng? headsetLocation = LatLng(earphoneLatitude, earphoneLongitude);
print("我的位置为:" + myLatitude.toString() + ',' + myLongitude.toString() + ",耳机的位置为" + earphoneLatitude.toString() + ',' + earphoneLongitude.toString());
if (headsetLocation != null) {
await map_launcher.MapLauncher.showMarker(
mapType: map_launcher.MapType.amap,
coords: map_launcher.Coords(
headsetLocation.latitude,
headsetLocation.longitude
),
title: widget.headset.classicName, // 使用设备名称
description: "耳机位置", // 可选描述
);
print('跳转成功');
} else {
print('无法获取耳机位置');
Fluttertoast.showToast(msg: "无法获取耳机位置");
}
} else {
print('高德地图未安装');
Fluttertoast.showToast(msg: "请先安装高德地图");
}
} catch (e) {
print('跳转失败: $e');
Fluttertoast.showToast(msg: "跳转失败");
}
Navigator.pop(context);
}
///跳转百度地图
Future<void> goBaiduMap() async{
try {
// 检查百度地图是否安装
bool isBaiduAvailable = await map_launcher.MapLauncher.isMapAvailable(map_launcher.MapType.baidu);
if (isBaiduAvailable) {
print('高德地图已安装,准备跳转');
//耳机的位置
LatLng? headsetLocation = LatLng(earphoneLatitude,earphoneLongitude);
print("我的位置为:" + myLatitude.toString() + ',' + myLongitude.toString() + ",耳机的位置为" + earphoneLatitude.toString() + ',' + earphoneLongitude.toString());
if (headsetLocation != null) {
await map_launcher.MapLauncher.showMarker(
mapType: map_launcher.MapType.baidu,
coords: map_launcher.Coords(
headsetLocation.latitude,
headsetLocation.longitude
),
title: widget.headset.classicName, // 使用设备名称
description: "耳机位置", // 可选描述
);
print('跳转成功');
} else {
print('无法获取耳机位置');
Fluttertoast.showToast(msg: "无法获取耳机位置");
}
} else {
print('高德地图未安装');
Fluttertoast.showToast(msg: "请先安装百度地图");
}
} catch (e) {
print('跳转失败: $e');
Fluttertoast.showToast(msg: "跳转失败");
}
Navigator.pop(context);
}
//====================================播放声音====================================
Widget _buildSoundCard() {
return
Positioned(
bottom: 170,
left: 20,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 10
),
height: 85,
width: 158,
decoration: BoxDecoration(
color: Color(0xFF333333).withOpacity(0.4),
borderRadius: BorderRadius.circular(10),
),
child:Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//图标
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFFF7F7F7),
),
child: Center(
child: Icon(Icons.play_arrow_outlined,color: Color(0xFFAACD06),size: 29,),
),
),
Row(
children: [
SizedBox(width: 5,),
Text(S.current.playAudio,style: TextStyle(color: Colors.white,fontSize: 13,fontWeight: FontWeight.bold),),
Spacer(),
//按键
GestureDetector(
onTap: (){
if(isFinding){
onStop();
}else{
onStart();
}
},
child: Container(
height: 14,
width: 28,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color:isFinding ? Color(0xFFAACD06): Color(0xFFF7F7F7) ,
),
child: Container(
margin: EdgeInsets.only(
left: isFinding ? 12 : 0,
right: isFinding ? 0 : 12,
),
height: 10,
width: 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isFinding ? Color(0xFFF7F7F7) : Color(0xFFAACD06) ,
),
),
),
)
],
)
],
),
)
);
}
//===============================导航========================================
Widget _buildNavigationCard() {
return
Positioned(
bottom: 170,
right: 20,
child: GestureDetector(
onTap: (){
//跳转弹窗
showBottomSheetDialog(context);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20,vertical: 10),
height: 85,
width: 158,
decoration: BoxDecoration(
color: Color(0xFF333333).withOpacity(0.4),
borderRadius: BorderRadius.circular(10),
),
child:Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//图标
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFFF7F7F7),
),
child: Center(
child: Transform.rotate(
angle: 45 * 3.1415926/180,
child:Icon(Icons.navigation_outlined,color: Color(0xFFAACD06),size:24,),
)
),
),
Row(
children: [
SizedBox(width: 5,),
Text(S.current.navigation,style: TextStyle(color: Colors.white,fontSize: 13,fontWeight: FontWeight.bold),),
Spacer(),
Text("$meter" + S.current.meter,style: TextStyle(fontSize: 12,color: Colors.white,),)
],
)
],
),
)
)
);
}
//==================================具体位置=======================
Widget _buildLocationCard() {
return
Positioned(
bottom: 20,
right: 20,
left: 20,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20,vertical: 10),
height: 140,
width: 325,
decoration: BoxDecoration(
color: Color(0xFF333333).withOpacity(0.4),
borderRadius: BorderRadius.circular(10),
),
child:Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.position,style: TextStyle(color: Colors.white,fontSize: 18,fontWeight: FontWeight.bold),),
//具体位置
Row(
children: [
//图标
Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFFF7F7F7),
),
child: Center(
child: Image.asset("assets/images/position_icon.png")
),
),
SizedBox(width: 15,),
Column(
children: [
//地址
Container(
width: 210,
child: Text(
address,
style: TextStyle(color: Colors.white, fontSize: 12),
softWrap: true,
maxLines: 4,
overflow: TextOverflow.ellipsis, // 超过4行显示省略号
),
),
//时间戳
Container(
width: 200,
child: Text(
time,
style: TextStyle(color: Colors.white, fontSize: 12),
),
)
],
)
],
)
],
),
)
);
}
}