调整地图样式
This commit is contained in:
@@ -1,13 +1,35 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
||||||
<title>路径规划</title>
|
<title>路径规划</title>
|
||||||
<style>
|
|
||||||
html, body, #container { width: 100%; height: 100%; margin: 0; }
|
|
||||||
|
|
||||||
/* 搜索栏样式 */
|
<style>
|
||||||
|
/* --- 全局样式 --- */
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 暴力隐藏高德自带的导流链接和Logo (关键) --- */
|
||||||
|
.amap-callamap,
|
||||||
|
.amap-lib-driving-callBtn,
|
||||||
|
.amap-copyright,
|
||||||
|
.amap-logo {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panel .amap-call {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 搜索栏样式 --- */
|
||||||
#search-box {
|
#search-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
@@ -16,46 +38,114 @@
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.input-row { display: flex; gap: 5px; }
|
|
||||||
input { flex: 1; padding: 8px; border: 1px solid #eee; border-radius: 4px; }
|
|
||||||
button { padding: 0 15px; background: #3366FF; color: #fff; border: none; border-radius: 4px; font-weight: bold; }
|
|
||||||
|
|
||||||
/* 导航结果面板 (仿高德原生) */
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0 15px;
|
||||||
|
height: 38px;
|
||||||
|
background: #3366FF;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 导航结果面板 (底部弹出) --- */
|
||||||
#panel {
|
#panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 30%; /* 占据底部30% */
|
height: 35%;
|
||||||
|
/* 面板高度 */
|
||||||
background-color: white;
|
background-color: white;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #eee;
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
display: none; /* 默认隐藏,规划成功后显示 */
|
display: none;
|
||||||
|
/* 默认隐藏 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 自定义定位按钮样式 --- */
|
||||||
|
#location-btn {
|
||||||
|
position: fixed;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 50px;
|
||||||
|
/* 默认位置 */
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
/* 圆形更符合现代审美 */
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 150;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: bottom 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
/* 平滑动画 */
|
||||||
|
}
|
||||||
|
|
||||||
|
#location-btn:active {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#location-btn svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
fill: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 关键:当 body 有 panel-active 类时,按钮上移 --- */
|
||||||
|
body.panel-active #location-btn {
|
||||||
|
bottom: 38%;
|
||||||
|
/* 对应 #panel 的 height + 一点间距 */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- 1. 必须最先配置安全密钥 -->
|
<!-- 1. 配置安全密钥 -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window._AMapSecurityConfig = {
|
window._AMapSecurityConfig = {
|
||||||
securityJsCode: '0529b72df6bf0c577ff2182cb8b1d970',
|
securityJsCode: '0529b72df6bf0c577ff2182cb8b1d970',
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 2. 加载地图和插件 -->
|
<!-- 2. 加载地图和插件 (去掉了 Geolocation 插件,避免弹窗) -->
|
||||||
<script src="https://webapi.amap.com/maps?v=2.0&key=2cc1d822e313307fe311c3127a1deeb5&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete,AMap.ToolBar,AMap.Geolocation,AMap.Scale"></script>
|
<script
|
||||||
|
src="https://webapi.amap.com/maps?v=2.0&key=2cc1d822e313307fe311c3127a1deeb5&plugin=AMap.MoveAnimation,AMap.Driving,AMap.TruckDriving,AMap.AutoComplete,AMap.ToolBar,AMap.Scale">
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="search-box">
|
<div id="search-box">
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<input id="startInput" placeholder="起点: 默认使用当前位置"/>
|
<input id="startInput" placeholder="起点: 默认使用当前位置" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<input id="endInput" placeholder="终点: 请输入目的地" />
|
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||||||
@@ -64,13 +154,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
|
|
||||||
<div id="panel"></div>
|
<div id="panel"></div>
|
||||||
|
|
||||||
|
<!-- 自定义定位按钮 -->
|
||||||
|
<div id="location-btn" onclick="backToLocation()">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M512 85.333333a426.666667 426.666667 0 1 0 0 853.333334 426.666667 426.666667 0 0 0 0-853.333334z m0 149.333334a277.333333 277.333333 0 1 1 0 554.666666 277.333333 277.333333 0 0 1 0-554.666666z m0 149.333333a128 128 0 1 0 0 256 128 128 0 0 0 0-256z"
|
||||||
|
fill="#666666"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var map, marker, driving, truckDriving;
|
var map, marker, driving, truckDriving;
|
||||||
var currentLat, currentLng;
|
var currentLat, currentLng;
|
||||||
|
|
||||||
// 标记是否使用货车模式 (true: 货车, false: 轿车)
|
|
||||||
var isTruckMode = false;
|
var isTruckMode = false;
|
||||||
|
|
||||||
function initMap() {
|
function initMap() {
|
||||||
@@ -80,40 +178,38 @@
|
|||||||
viewMode: '3D'
|
viewMode: '3D'
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 新增:添加地图控件 ---
|
// 通知 Flutter 地图加载完毕
|
||||||
map.addControl(new AMap.Scale()); // 比例尺
|
map.on('complete', function () {
|
||||||
map.addControl(new AMap.ToolBar({
|
console.log("JS->: Map is ready.");
|
||||||
position: {
|
if (window.flutter_inappwebview) {
|
||||||
bottom: '80px',
|
window.flutter_inappwebview.callHandler('mapReady');
|
||||||
right: '10px'
|
|
||||||
}
|
}
|
||||||
})); // 缩放
|
});
|
||||||
|
|
||||||
map.addControl(new AMap.Geolocation({ // 定位
|
// 添加基础控件
|
||||||
enableHighAccuracy: true,
|
map.addControl(new AMap.Scale());
|
||||||
timeout: 10000,
|
map.addControl(new AMap.ToolBar({
|
||||||
zoomToAccuracy: true,
|
visible: true,
|
||||||
position: {
|
position: {
|
||||||
bottom: '20px',
|
top: '200px',
|
||||||
right: '10px'
|
right: '10px'
|
||||||
}
|
} // 稍微避开定位按钮
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 初始化轿车规划 ---
|
// --- 初始化轿车规划 ---
|
||||||
driving = new AMap.Driving({
|
driving = new AMap.Driving({
|
||||||
map: map,
|
map: map,
|
||||||
panel: "panel", // 结果显示在 panel div 中
|
panel: "panel",
|
||||||
policy: AMap.DrivingPolicy.LEAST_TIME
|
policy: AMap.DrivingPolicy.LEAST_TIME
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 初始化货车规划 (按需开启) ---
|
// --- 初始化货车规划 ---
|
||||||
// 货车需要设置 size (1:微型, 2:轻型, 3:中型, 4:重型)
|
|
||||||
if (AMap.TruckDriving) {
|
if (AMap.TruckDriving) {
|
||||||
truckDriving = new AMap.TruckDriving({
|
truckDriving = new AMap.TruckDriving({
|
||||||
map: map,
|
map: map,
|
||||||
panel: "panel",
|
panel: "panel",
|
||||||
size: 2,
|
size: 2,
|
||||||
policy: 0, // 0: 躲避拥堵
|
policy: 0,
|
||||||
width: 2.5,
|
width: 2.5,
|
||||||
height: 2,
|
height: 2,
|
||||||
load: 1,
|
load: 1,
|
||||||
@@ -122,18 +218,26 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 输入提示 (自动补全) ---
|
// --- 输入提示 ---
|
||||||
new AMap.AutoComplete({ input: "startInput" });
|
new AMap.AutoComplete({
|
||||||
new AMap.AutoComplete({ input: "endInput" });
|
input: "endInput"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 被动接收 Flutter 传来的定位 ---
|
/**
|
||||||
|
* 核心功能 1: 接收 Flutter 传来的定位数据
|
||||||
|
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
||||||
|
*/
|
||||||
function updateMyLocation(lat, lng, angle) {
|
function updateMyLocation(lat, lng, angle) {
|
||||||
|
// 1. 更新全局变量,供 backToLocation 使用
|
||||||
currentLat = lat;
|
currentLat = lat;
|
||||||
currentLng = lng;
|
currentLng = lng;
|
||||||
|
|
||||||
var position = [lng, lat];
|
var position = [lng, lat];
|
||||||
|
|
||||||
|
// 2. 绘制或移动 Marker (这就是所谓的“原有逻辑”)
|
||||||
if (!marker) {
|
if (!marker) {
|
||||||
|
// 如果是第一次收到定位,创建 Marker
|
||||||
marker = new AMap.Marker({
|
marker = new AMap.Marker({
|
||||||
map: map,
|
map: map,
|
||||||
position: position,
|
position: position,
|
||||||
@@ -142,87 +246,95 @@
|
|||||||
autoRotation: true,
|
autoRotation: true,
|
||||||
angle: angle || 0,
|
angle: angle || 0,
|
||||||
});
|
});
|
||||||
// 第一次定位移动中心
|
map.setCenter(position); // 首次定位自动居中
|
||||||
map.setCenter(position);
|
|
||||||
|
// 自动填充起点输入框
|
||||||
|
document.getElementById('startInput').value = "我的位置";
|
||||||
} else {
|
} else {
|
||||||
marker.setPosition(position);
|
// 后续定位,使用动画平滑移动
|
||||||
if (angle) marker.setAngle(angle);
|
marker.moveTo(position, {
|
||||||
|
duration: 1000, // 假设 Flutter 每 1s 传一次数据
|
||||||
|
autoRotation: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 核心:规划路线 ---
|
/**
|
||||||
|
* 核心功能 2: 点击按钮回到当前位置
|
||||||
|
*/
|
||||||
|
function backToLocation() {
|
||||||
|
if (currentLng && currentLat) {
|
||||||
|
map.panTo([currentLng, currentLat]);
|
||||||
|
map.setZoom(17);
|
||||||
|
console.log("JS: 已回到当前位置");
|
||||||
|
} else {
|
||||||
|
console.log("JS: 暂无定位数据,向 Flutter 请求...");
|
||||||
|
if (window.flutter_inappwebview) {
|
||||||
|
window.flutter_inappwebview.callHandler('requestLocation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心功能 3: 路径规划
|
||||||
|
*/
|
||||||
function startRouteSearch() {
|
function startRouteSearch() {
|
||||||
var startKw = document.getElementById('startInput').value;
|
|
||||||
var endKw = document.getElementById('endInput').value;
|
var endKw = document.getElementById('endInput').value;
|
||||||
|
|
||||||
if (!endKw) {
|
if (!endKw) {
|
||||||
console.log("FlutterLog: 终点不能为空");
|
console.log("JS: 终点不能为空");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("FlutterLog: 开始规划..." + (isTruckMode ? "货车" : "轿车"));
|
// 清除旧路线
|
||||||
|
|
||||||
// 清除之前的路线
|
|
||||||
if (driving) driving.clear();
|
if (driving) driving.clear();
|
||||||
if (truckDriving) truckDriving.clear();
|
if (truckDriving) truckDriving.clear();
|
||||||
|
|
||||||
// 构造起点和终点参数
|
// 收起键盘
|
||||||
// 高德API Search方法支持点对象数组:[{keyword: '名字', city: '城市'}, {keyword: '...'}]
|
document.getElementById('endInput').blur();
|
||||||
|
|
||||||
|
// 构造参数
|
||||||
var points = [];
|
var points = [];
|
||||||
|
|
||||||
// 1. 处理起点
|
// 起点:默认使用当前定位
|
||||||
if (startKw) {
|
|
||||||
// 如果用户输入了文字
|
|
||||||
points.push({ keyword: startKw });
|
|
||||||
} else {
|
|
||||||
// 如果用户没输入,使用当前定位
|
|
||||||
if (currentLng && currentLat) {
|
if (currentLng && currentLat) {
|
||||||
// 关键点:当混合使用坐标和关键字时,必须构建 LngLat 对象
|
|
||||||
points.push({
|
points.push({
|
||||||
keyword: '我的位置',
|
keyword: '我的位置',
|
||||||
location: new AMap.LngLat(currentLng, currentLat)
|
location: new AMap.LngLat(currentLng, currentLat)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("FlutterLog: 无定位数据且无输入");
|
alert("未获取到定位,请稍后再试");
|
||||||
alert("未获取到定位,请输入起点");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 处理终点
|
// 终点
|
||||||
points.push({ keyword: endKw });
|
points.push({
|
||||||
|
keyword: endKw
|
||||||
|
});
|
||||||
|
|
||||||
// 显示底部面板
|
// 开始搜索 (以轿车为例)
|
||||||
document.getElementById('panel').style.display = 'block';
|
|
||||||
|
|
||||||
// 3. 发起请求
|
|
||||||
if (isTruckMode && truckDriving) {
|
|
||||||
// 货车接口略有不同,需要传入 path 数组
|
|
||||||
// truckDriving.search(path, callback)
|
|
||||||
// 这里为了简化,我们先演示轿车。货车通常需要具体的经纬度,建议先通过 Geocoder 把 endKw 转成经纬度再传给货车接口
|
|
||||||
console.log("FlutterLog: 货车API需要更严格的经纬度参数,建议先使用轿车演示");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认使用轿车规划 (支持 keyword + location 混合)
|
|
||||||
driving.search(points, function (status, result) {
|
driving.search(points, function (status, result) {
|
||||||
if (status === 'complete') {
|
if (status === 'complete') {
|
||||||
console.log('FlutterLog: 规划成功');
|
console.log('JS: 规划成功');
|
||||||
|
|
||||||
|
// --- 成功后的 UI 联动 (这就是“原有逻辑”中负责界面的部分) ---
|
||||||
|
var panel = document.getElementById('panel');
|
||||||
|
panel.style.display = 'block';
|
||||||
|
|
||||||
|
// 给 body 加类名,让定位按钮上移
|
||||||
|
document.body.classList.add('panel-active');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log('FlutterLog: 规划失败: ' + result);
|
console.log('JS: 规划失败: ' + result);
|
||||||
// 常见错误: "USER_DAILY_QUERY_OVER_LIMIT" (Key额度超限)
|
alert("规划失败,请检查地点名称");
|
||||||
// "INVALID_USER_KEY" (Key错误)
|
|
||||||
// "no_data" (地点没找到)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 绘制自定义路径 (保持原有功能) ---
|
// 辅助功能:手动绘制路线 (Flutter 可能用到)
|
||||||
function drawCustomRoute(pointsJsonString) {
|
function drawCustomRoute(pointsJsonString) {
|
||||||
// 如果是从Flutter传来的JSON字符串,需要Parse,如果是对象则直接用
|
|
||||||
var path = typeof pointsJsonString === 'string' ? JSON.parse(pointsJsonString) : pointsJsonString;
|
var path = typeof pointsJsonString === 'string' ? JSON.parse(pointsJsonString) : pointsJsonString;
|
||||||
|
|
||||||
if (driving) driving.clear();
|
if (driving) driving.clear();
|
||||||
|
|
||||||
var polyline = new AMap.Polyline({
|
var polyline = new AMap.Polyline({
|
||||||
path: path,
|
path: path,
|
||||||
isOutline: true,
|
isOutline: true,
|
||||||
@@ -239,4 +351,5 @@
|
|||||||
window.onload = initMap;
|
window.onload = initMap;
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -4,124 +4,133 @@ import 'dart:convert';
|
|||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart'; // 确保引用这个做权限请求
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class MapController extends GetxController with BaseControllerMixin {
|
class MapController extends GetxController with BaseControllerMixin ,WidgetsBindingObserver{
|
||||||
@override
|
@override
|
||||||
String get builderId => 'map';
|
String get builderId => 'map';
|
||||||
|
|
||||||
InAppWebViewController? webViewController;
|
InAppWebViewController? webViewController;
|
||||||
StreamSubscription<Position>? positionStream;
|
StreamSubscription<Position>? positionStream;
|
||||||
final RxBool isLocationPermissionGranted = false.obs;
|
|
||||||
|
|
||||||
// 缓存最后一次的位置,防止 WebView 加载慢于定位
|
// --- 状态标志 ---
|
||||||
|
final RxBool isLocationPermissionGranted = false.obs;
|
||||||
|
final RxBool isMapReady = false.obs; // 跟踪地图是否加载完成
|
||||||
|
|
||||||
Position? _lastKnownPosition;
|
Position? _lastKnownPosition;
|
||||||
// 是否已经初始化过地图中心
|
|
||||||
bool _hasInitializedMapCenter = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
WidgetsBinding.instance.addObserver(this); // 注册监听
|
||||||
requestLocationPermission();
|
requestLocationPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this); // 移除监听
|
||||||
positionStream?.cancel();
|
positionStream?.cancel();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求权限
|
@override
|
||||||
void requestLocationPermission() async {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
var status = await Permission.locationWhenInUse.request();
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
// 回到前台,如果之前有权限且开启过,恢复定位
|
||||||
|
if(isLocationPermissionGranted.value) _startLocationTracking();
|
||||||
|
} else if (state == AppLifecycleState.paused) {
|
||||||
|
// 切到后台,取消定位流
|
||||||
|
positionStream?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print("权限状态: $status"); // 在控制台看这个输出
|
/// WebView 创建完成时调用
|
||||||
|
void onWebViewCreated(InAppWebViewController controller) {
|
||||||
if (status.isGranted) {
|
webViewController = controller;
|
||||||
isLocationPermissionGranted.value = true;
|
// 添加 JS Handler 来监听来自网页的 'mapReady' 事件
|
||||||
showToast('定位权限已获取');
|
controller.addJavaScriptHandler(
|
||||||
|
handlerName: 'mapReady',
|
||||||
|
callback: (args) {
|
||||||
|
print("Flutter<-JS: Map is ready, starting location tracking.");
|
||||||
|
isMapReady.value = true;
|
||||||
|
// 地图就绪后,如果已有权限和缓存的定位,则开始定位
|
||||||
|
if (isLocationPermissionGranted.value) {
|
||||||
_startLocationTracking();
|
_startLocationTracking();
|
||||||
}
|
}
|
||||||
else if (status.isPermanentlyDenied) {
|
},
|
||||||
// iOS 特性:如果你之前点过“不允许”,之后再请求都会直接进这里
|
);
|
||||||
isLocationPermissionGranted.value = false;
|
|
||||||
// 引导用户去设置页面开启
|
|
||||||
openAppSettings();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 第一次被拒绝,或者其他情况
|
|
||||||
isLocationPermissionGranted.value = false;
|
|
||||||
showErrorToast('需要定位权限才能显示您的位置');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/// 优化后的权限请求函数
|
||||||
|
void requestLocationPermission() async {
|
||||||
// 开启定位监听
|
|
||||||
void _startLocationTracking() async {
|
|
||||||
// 再次检查服务是否开启
|
|
||||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
if (!serviceEnabled) {
|
if (!serviceEnabled) {
|
||||||
|
showErrorToast('请开启手机的定位服务');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var status = await Permission.locationWhenInUse.request();
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case PermissionStatus.granted:
|
||||||
|
isLocationPermissionGranted.value = true;
|
||||||
|
// 权限已获取,如果地图也已就绪,则开始定位
|
||||||
|
if (isMapReady.value) {
|
||||||
|
_startLocationTracking();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PermissionStatus.denied:
|
||||||
|
showErrorToast('需要定位权限才能显示您的位置');
|
||||||
|
break;
|
||||||
|
case PermissionStatus.permanentlyDenied:
|
||||||
|
showErrorToast('定位权限已被永久拒绝,请到设置中手动开启');
|
||||||
|
openAppSettings();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showErrorToast('获取定位权限时发生未知错误');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 开始定位监听 (不再需要 delay)
|
||||||
|
void _startLocationTracking() async {
|
||||||
|
if (!isLocationPermissionGranted.value ||
|
||||||
|
!await Geolocator.isLocationServiceEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocationSettings locationSettings = LocationSettings(
|
const LocationSettings locationSettings = LocationSettings(
|
||||||
accuracy: LocationAccuracy.high,
|
accuracy: LocationAccuracy.high,
|
||||||
distanceFilter: 50, // 移动更新范围
|
distanceFilter: 10,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 先获取一次当前位置,用于快速初始化中心
|
// 立即获取一次当前位置,用于快速初始化
|
||||||
Geolocator.getCurrentPosition().then((position) {
|
try {
|
||||||
|
Position position = await Geolocator.getCurrentPosition();
|
||||||
_lastKnownPosition = position;
|
_lastKnownPosition = position;
|
||||||
_syncLocationToMap(position, isInit: true);
|
_syncLocationToMap(position);
|
||||||
});
|
} catch (e) {
|
||||||
|
print("Error getting initial position: $e");
|
||||||
|
}
|
||||||
|
|
||||||
// 持续监听
|
// 持续监听位置变化
|
||||||
|
positionStream?.cancel(); // 先取消旧的监听
|
||||||
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
|
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
|
||||||
.listen((Position position) {
|
.listen((Position position) {
|
||||||
_lastKnownPosition = position;
|
_lastKnownPosition = position;
|
||||||
_syncLocationToMap(position, isInit: false);
|
_syncLocationToMap(position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将位置同步给 JS
|
// 将 Flutter 的定位数据同步给 JS
|
||||||
void _syncLocationToMap(Position position, {required bool isInit}) {
|
void _syncLocationToMap(Position position) {
|
||||||
if (webViewController == null) return;
|
if (webViewController == null || !isMapReady.value) return;
|
||||||
|
// 只有当 heading 有效且大于 0 时才传给 JS,否则传 null,让 JS 保持上一次的角度
|
||||||
|
String headingVal = (position.heading > 0) ? "${position.heading}" : "null";
|
||||||
|
|
||||||
// 如果是第一次定位,或者 WebView 刚加载完且从未设置过中心
|
|
||||||
if (isInit || !_hasInitializedMapCenter) {
|
|
||||||
webViewController!.evaluateJavascript(
|
webViewController!.evaluateJavascript(
|
||||||
source: "initLocation(${position.latitude}, ${position.longitude})"
|
source:
|
||||||
);
|
"updateMyLocation(${position.latitude}, ${position.longitude}, ${position.heading})",
|
||||||
_hasInitializedMapCenter = true;
|
|
||||||
} else {
|
|
||||||
// 后续只更新图标位置,不强行移动地图中心(防止用户拖动地图看别处时被拉回)
|
|
||||||
webViewController!.evaluateJavascript(
|
|
||||||
source: "updateMyLocation(${position.latitude}, ${position.longitude}, ${position.heading})"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebView 加载完成回调
|
|
||||||
void onWebViewLoadStop() {
|
|
||||||
// 页面加载完了,如果我们已经拿到了定位数据,立刻设置地图中心
|
|
||||||
if (_lastKnownPosition != null) {
|
|
||||||
_syncLocationToMap(_lastKnownPosition!, isInit: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果业务需要,可以在这里加载默认的推荐路径
|
|
||||||
// loadRecommendedRoute();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟:获取你的后台推荐路径
|
|
||||||
void loadRecommendedRoute() {
|
|
||||||
// 示例数据
|
|
||||||
List<List<double>> routePoints = [
|
|
||||||
[116.397428, 39.90923],
|
|
||||||
[116.398000, 39.90950],
|
|
||||||
[116.400000, 39.91000],
|
|
||||||
];
|
|
||||||
|
|
||||||
String jsonPoints = jsonEncode(routePoints);
|
|
||||||
webViewController?.evaluateJavascript(source: "drawCustomRoute($jsonPoints)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:getx_scaffold/getx_scaffold.dart';
|
import 'package:getx_scaffold/getx_scaffold.dart';
|
||||||
|
|
||||||
@@ -10,23 +11,21 @@ class MapPage extends GetView<MapController> {
|
|||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
InAppWebView(
|
InAppWebView(
|
||||||
// 确保 pubspec.yaml 中声明了 assets/html/map.html
|
|
||||||
initialFile: 'assets/html/map.html',
|
initialFile: 'assets/html/map.html',
|
||||||
initialSettings: InAppWebViewSettings(
|
initialSettings: InAppWebViewSettings(
|
||||||
isInspectable: true,
|
isInspectable: true,
|
||||||
geolocationEnabled: true,
|
// 既然完全依赖 Flutter 定位,建议把 WebView 的定位彻底关掉,防止 JS 意外触发
|
||||||
// 允许 JS 弹窗 (Alert) 用于调试
|
geolocationEnabled: false,
|
||||||
javaScriptCanOpenWindowsAutomatically: true,
|
javaScriptEnabled: true,
|
||||||
|
// Android 混合开发推荐配置
|
||||||
|
useHybridComposition: true, // 提升地图渲染性能(重要)
|
||||||
|
allowFileAccessFromFileURLs: true, // 允许本地 html 访问本地资源
|
||||||
|
allowUniversalAccessFromFileURLs: true,
|
||||||
),
|
),
|
||||||
onWebViewCreated: (c) {
|
// 当 WebView 创建完成后,将 controller 实例传递给我们的 MapController
|
||||||
controller.webViewController = c;
|
onWebViewCreated: controller.onWebViewCreated,
|
||||||
},
|
|
||||||
onLoadStop: (c, url) {
|
|
||||||
// 通知 Controller 页面加载完毕
|
|
||||||
controller.onWebViewLoadStop();
|
|
||||||
},
|
|
||||||
onConsoleMessage: (controller, consoleMessage) {
|
onConsoleMessage: (controller, consoleMessage) {
|
||||||
// 方便在 Flutter 控制台看 JS 日志
|
// 方便在 Flutter 控制台查看来自 JS 的日志
|
||||||
print("JS Log: ${consoleMessage.message}");
|
print("JS Log: ${consoleMessage.message}");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user