Compare commits
82 Commits
1278c38b7e
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 881da166d1 | |||
| 14b2e6b35c | |||
| 20c39a4a12 | |||
|
|
5c74c7ccc0 | ||
| 23fc0da5c8 | |||
| 7efd933416 | |||
| 2b33dac384 | |||
| ef60b8ed62 | |||
| bec6952707 | |||
| f68349a208 | |||
|
|
08dcc64ef4 | ||
| 9242dcaf70 | |||
| 95fdfe6269 | |||
| ee88b3ada9 | |||
| ba27799c41 | |||
| c527732532 | |||
| cd223fa9bf | |||
| fdd6653fce | |||
| 8748a60f7f | |||
|
|
5843ef6e87 | ||
| 0123025296 | |||
| 587dd48896 | |||
| 569780ffb4 | |||
| 3aac4e7330 | |||
| b275c66383 | |||
| bf80931f37 | |||
| 67c36f436c | |||
| c10c92afdc | |||
| d3edf95df3 | |||
| 930953780d | |||
| a497bc6469 | |||
| 3f282d15c1 | |||
| 03b35f660c | |||
| 9b6f93ca95 | |||
| eb41ecaec4 | |||
| 384de27f2c | |||
| 84b174c4a5 | |||
| cd14469d79 | |||
| b846d352a2 | |||
|
|
a24f41a8d5 | ||
|
|
65b4a3ac34 | ||
| 02e1946319 | |||
| d5083c1939 | |||
| fe44848529 | |||
| 572c416827 | |||
| 8df16f0787 | |||
| ce6bd3edd2 | |||
| 6997b4ac9e | |||
| a26d2478f3 | |||
| 0dded3b928 | |||
| b7caf58adf | |||
| 0df1902df2 | |||
| a8314d8a7a | |||
| 39cae906e9 | |||
| 14fd6c75d0 | |||
| 1724852a39 | |||
| a05d4ebb9b | |||
| 600cea4379 | |||
| 3dfc1dfc2c | |||
| 909dc95771 | |||
| cf0896453b | |||
| dce9718320 | |||
| 4491aa9b91 | |||
| 5364612a6f | |||
| 10867178fa | |||
| a5e2a89e4f | |||
| 26c5f9d67a | |||
| 9cd87b0535 | |||
| 45e45d8160 | |||
| 87e890f97e | |||
| dcf925b8c1 | |||
| c45863eda6 | |||
| 756bf53cf5 | |||
| f68c2d0938 | |||
| 211d0225e4 | |||
| 7d9b4d99e8 | |||
| 3dd583a278 | |||
| 35bd3a78a5 | |||
| 032e60d362 | |||
| 45f5035d1b | |||
| f792915429 | |||
| edbacc502b |
8
ln_jq_app/.claude/settings.local.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(flutter build apk --debug 2>&1 | tail -15)",
|
||||
"Bash(flutter build apk --debug 2>&1 | tail -10)"
|
||||
]
|
||||
}
|
||||
}
|
||||
7
ln_jq_app/android/.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(./gradlew assembleDebug --stacktrace)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ android {
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = 6
|
||||
versionName = "1.2.3"
|
||||
versionCode = 7
|
||||
versionName = "1.2.4"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -68,3 +68,9 @@ android {
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.amap.api:navi-3dmap-location-search:10.0.700_3dmap10.0.700_loc6.4.5_sea9.7.2")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
}
|
||||
|
||||
27
ln_jq_app/android/app/proguard-rules.pro
vendored
@@ -59,3 +59,30 @@
|
||||
-dontwarn anetwork.**
|
||||
-dontwarn com.ut.**
|
||||
-dontwarn com.ta.**
|
||||
|
||||
|
||||
#3D 地图 V5.0.0之前:
|
||||
-keep class com.amap.api.maps.**{*;}
|
||||
-keep class com.autonavi.amap.mapcore.*{*;}
|
||||
-keep class com.amap.api.trace.**{*;}
|
||||
|
||||
#3D 地图 V5.0.0之后:
|
||||
-keep class com.amap.api.maps.**{*;}
|
||||
-keep class com.autonavi.**{*;}
|
||||
-keep class com.amap.api.trace.**{*;}
|
||||
|
||||
#定位
|
||||
-keep class com.amap.api.location.**{*;}
|
||||
-keep class com.amap.api.fence.**{*;}
|
||||
-keep class com.autonavi.aps.amapapi.model.**{*;}
|
||||
|
||||
#搜索
|
||||
-keep class com.amap.api.services.**{*;}
|
||||
|
||||
#2D地图
|
||||
-keep class com.amap.api.maps2d.**{*;}
|
||||
-keep class com.amap.api.mapcore2d.**{*;}
|
||||
|
||||
#导航
|
||||
-keep class com.amap.api.navi.**{*;}
|
||||
-keep class com.autonavi.**{*;}
|
||||
@@ -1,35 +1,70 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<!--定位权限-->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<!--用于申请调用A-GPS模块-->
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission>
|
||||
<!--如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!--如果您的应用需要后台定位权限,且有可能运行在Android Q设备上,并且设置了target>28,必须增加这个权限声明-->
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="小羚羚"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/logo">
|
||||
android:icon="@mipmap/logo"
|
||||
android:label="小羚羚">
|
||||
|
||||
<!-- 高德地图Key -->
|
||||
<meta-data
|
||||
android:name="com.amap.api.v2.apikey"
|
||||
android:value="92495660f7bc990cb475426c47c03b65" />
|
||||
|
||||
<!-- 高德地图定位服务 -->
|
||||
<service android:name="com.amap.api.location.APSService" />
|
||||
<!--高德导航-->
|
||||
<activity
|
||||
android:name="com.amap.api.navi.AmapRouteActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|navigation" />
|
||||
<!--自定义导航 Activity-->
|
||||
<activity
|
||||
android:name="com.lnkj.ln_jq_app.NavigationActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|navigation"
|
||||
android:screenOrientation="portrait" />
|
||||
<!--搜索目的地 Activity-->
|
||||
<activity
|
||||
android:name="com.lnkj.ln_jq_app.SearchDestinationActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|navigation"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
@@ -37,11 +72,10 @@
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
@@ -51,30 +85,13 @@
|
||||
android:value="2" />
|
||||
|
||||
<!-- 请填写你自己的- appKey -->
|
||||
<meta-data android:name="com.alibaba.app.appkey" android:value="335642645"/>
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appkey"
|
||||
android:value="335642645" />
|
||||
<!-- 请填写你自己的appSecret -->
|
||||
<meta-data android:name="com.alibaba.app.appsecret" android:value="39628204345a4240b5b645b68a5896c7"/>
|
||||
<!-- 华为通道的参数appid -->
|
||||
<meta-data android:name="com.huawei.hms.client.appid" android:value="" />
|
||||
|
||||
<!-- vivo通道的参数api_key为appkey -->
|
||||
<meta-data android:name="com.vivo.push.api_key" android:value="" />
|
||||
<meta-data android:name="com.vivo.push.app_id" android:value="" />
|
||||
|
||||
<!-- honor通道的参数-->
|
||||
<meta-data android:name="com.hihonor.push.app_id" android:value="" />
|
||||
|
||||
<!-- oppo -->
|
||||
<meta-data android:name="com.oppo.push.key" android:value="" />
|
||||
<meta-data android:name="com.oppo.push.secret" android:value="" />
|
||||
|
||||
<!-- 小米-->
|
||||
<meta-data android:name="com.xiaomi.push.id" android:value="id=2222222222222222222" />
|
||||
<meta-data android:name="com.xiaomi.push.key" android:value="id=5555555555555" />
|
||||
|
||||
<!-- 魅族-->
|
||||
<meta-data android:name="com.meizu.push.id" android:value="" />
|
||||
<meta-data android:name="com.meizu.push.key" android:value="" />
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appsecret"
|
||||
android:value="39628204345a4240b5b645b68a5896c7" />
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +116,7 @@
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
@@ -117,8 +135,8 @@
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -1,6 +1,161 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
|
||||
private static final String CHANNEL = "com.lnkj.ln_jq_app/map";
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
// 权限请求码
|
||||
private static final int PERMISSION_REQUEST_CODE = 1001;
|
||||
|
||||
private NativeMapView mapView;
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine);
|
||||
|
||||
// 注册高德地图导航Platform View
|
||||
flutterEngine
|
||||
.getPlatformViewsController()
|
||||
.getRegistry()
|
||||
.registerViewFactory(
|
||||
"NativeFirstPage",
|
||||
new NativeMapFactory(this)
|
||||
);
|
||||
|
||||
// 设置 Activity 实例到地图
|
||||
NativeMapFactory.setActivity(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前系统版本需要申请的权限列表
|
||||
*/
|
||||
private String[] getRequiredPermissions() {
|
||||
List<String> permissions = new ArrayList<>();
|
||||
// 定位权限是必须的
|
||||
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||
|
||||
// 存储权限处理:Android 13 (API 33) 以下才需要申请 legacy 存储权限
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
return permissions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并申请权限
|
||||
*/
|
||||
private void checkAndRequestPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
String[] requiredPermissions = getRequiredPermissions();
|
||||
List<String> deniedPermissions = new ArrayList<>();
|
||||
|
||||
for (String permission : requiredPermissions) {
|
||||
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
deniedPermissions.add(permission);
|
||||
}
|
||||
}
|
||||
|
||||
if (!deniedPermissions.isEmpty()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
deniedPermissions.toArray(new String[0]),
|
||||
PERMISSION_REQUEST_CODE
|
||||
);
|
||||
} else {
|
||||
Log.d(TAG, "所有必要权限已授予");
|
||||
if (mapView != null) {
|
||||
mapView.startLocation();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mapView != null) {
|
||||
mapView.startLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
public void setMapView(NativeMapView mapView) {
|
||||
this.mapView = mapView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mapView != null) {
|
||||
mapView.dispose();
|
||||
mapView = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// 处理从搜索目的地页面返回的结果
|
||||
if (requestCode == 1000 && resultCode == RESULT_OK && data != null) {
|
||||
String name = data.getStringExtra(SearchDestinationActivity.EXTRA_RESULT_NAME);
|
||||
String address = data.getStringExtra(SearchDestinationActivity.EXTRA_RESULT_ADDRESS);
|
||||
double lat = data.getDoubleExtra(SearchDestinationActivity.EXTRA_RESULT_LAT, 0);
|
||||
double lon = data.getDoubleExtra(SearchDestinationActivity.EXTRA_RESULT_LON, 0);
|
||||
String district = data.getStringExtra(SearchDestinationActivity.EXTRA_RESULT_DISTRICT);
|
||||
|
||||
if (name != null && lat != 0 && lon != 0) {
|
||||
// 获取地图实例并设置目的地
|
||||
NativeMapView mapView = NativeMapFactory.getMapView();
|
||||
if (mapView != null) {
|
||||
mapView.setDestination(name, address, lat, lon, district);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
boolean locationGranted = false;
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i])
|
||||
&& grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||
locationGranted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (locationGranted) {
|
||||
if (mapView != null) {
|
||||
mapView.startLocation();
|
||||
}
|
||||
} else {
|
||||
// 只有在定位权限确实被拒绝时才弹出提示
|
||||
Toast.makeText(this, "请授予应用定位权限以正常使用地图功能", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import io.flutter.plugin.common.MessageCodec;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
import io.flutter.plugin.platform.PlatformViewFactory;
|
||||
|
||||
/**
|
||||
* 高德地图导航 Platform View Factory
|
||||
* 对应iOS的NativeViewFactory
|
||||
*/
|
||||
public class NativeMapFactory extends PlatformViewFactory {
|
||||
|
||||
private static final String VIEW_TYPE_ID = "NativeFirstPage";
|
||||
private static NativeMapView mapViewInstance = null;
|
||||
private final Context context;
|
||||
|
||||
public NativeMapFactory(Context context) {
|
||||
super(StandardMessageCodec.INSTANCE);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformView create(Context context, int viewId, Object args) {
|
||||
mapViewInstance = new NativeMapView(context, viewId, args);
|
||||
return mapViewInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地图实例,供MainActivity使用
|
||||
*/
|
||||
public static NativeMapView getMapView() {
|
||||
return mapViewInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Activity 实例
|
||||
*/
|
||||
public static void setActivity(Activity activity) {
|
||||
if (mapViewInstance != null) {
|
||||
mapViewInstance.setActivity(activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.amap.api.maps.AMapException;
|
||||
import com.amap.api.navi.AMapNavi;
|
||||
import com.amap.api.navi.AMapNaviListener;
|
||||
import com.amap.api.navi.AMapNaviView;
|
||||
import com.amap.api.navi.AMapNaviViewListener;
|
||||
import com.amap.api.navi.AMapNaviViewOptions;
|
||||
import com.amap.api.navi.enums.NaviType;
|
||||
import com.amap.api.navi.enums.PathPlanningStrategy;
|
||||
import com.amap.api.navi.model.AMapCalcRouteResult;
|
||||
import com.amap.api.navi.model.AMapCarInfo;
|
||||
import com.amap.api.navi.model.AMapLaneInfo;
|
||||
import com.amap.api.navi.model.AMapModelCross;
|
||||
import com.amap.api.navi.model.AMapNaviCameraInfo;
|
||||
import com.amap.api.navi.model.AMapNaviCross;
|
||||
import com.amap.api.navi.model.AMapNaviLocation;
|
||||
import com.amap.api.navi.model.AMapNaviRouteNotifyData;
|
||||
import com.amap.api.navi.model.AMapNaviTrafficFacilityInfo;
|
||||
import com.amap.api.navi.model.AMapServiceAreaInfo;
|
||||
import com.amap.api.navi.model.AimLessModeCongestionInfo;
|
||||
import com.amap.api.navi.model.AimLessModeStat;
|
||||
import com.amap.api.navi.model.NaviInfo;
|
||||
import com.amap.api.navi.model.NaviPoi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* 导航
|
||||
*/
|
||||
public class NavigationActivity extends ComponentActivity implements AMapNaviListener {
|
||||
|
||||
private static final String TAG = "NavigationActivity";
|
||||
|
||||
private AMapNavi mAMapNavi;
|
||||
private AMapNaviView naviView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// 创建导航视图
|
||||
naviView = new AMapNaviView(this);
|
||||
naviView.onCreate(savedInstanceState);
|
||||
naviView.setAMapNaviViewListener(new AMapNaviViewListener() {
|
||||
@Override
|
||||
public void onNaviSetting() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviCancel() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNaviBackClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviMapMode(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviTurnClick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNextRoadClick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScanViewButtonClick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockMap(boolean b) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviViewLoaded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMapTypeChanged(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviViewShowMode(int i) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
AMapNaviViewOptions aMapNaviViewOptions = new AMapNaviViewOptions();
|
||||
Bitmap carBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.car);
|
||||
aMapNaviViewOptions.setCarBitmap(carBitmap);
|
||||
aMapNaviViewOptions.setSettingMenuEnabled(false);
|
||||
naviView.setViewOptions(aMapNaviViewOptions);
|
||||
|
||||
// 创建 AMapNavi 实例
|
||||
try {
|
||||
mAMapNavi = AMapNavi.getInstance(this);
|
||||
mAMapNavi.addAMapNaviListener(this);
|
||||
} catch (AMapException e) {
|
||||
Log.e(TAG, "初始化导航失败", e);
|
||||
Toast.makeText(this, "初始化导航失败", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到容器
|
||||
FrameLayout container = new FrameLayout(this);
|
||||
container.addView(naviView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
setContentView(container);
|
||||
|
||||
// 获取 Intent 中的参数
|
||||
processIntent(getIntent());
|
||||
|
||||
// 处理返回键
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
Log.d(TAG, "NavigationActivity created");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
processIntent(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Intent 参数并启动导航
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void processIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "Intent is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取起点、终点和途经点
|
||||
NaviPoi startNaviPoi = (NaviPoi) intent.getParcelableExtra("startPoi");
|
||||
NaviPoi endNaviPoi = (NaviPoi) intent.getParcelableExtra("endPoi");
|
||||
List<NaviPoi> wayPoints = intent.getParcelableArrayListExtra("wayPoints");
|
||||
|
||||
if (wayPoints != null) {
|
||||
Log.d("Navi", "获取到途径点数量: " + wayPoints.size());
|
||||
}
|
||||
|
||||
|
||||
if (startNaviPoi == null || endNaviPoi == null) {
|
||||
Log.e(TAG, "Missing start or end point");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置车辆信息
|
||||
AMapCarInfo carInfo = (AMapCarInfo) intent.getParcelableExtra("carInfo");
|
||||
if (carInfo != null) {
|
||||
mAMapNavi.setCarInfo(carInfo);
|
||||
}
|
||||
|
||||
mAMapNavi.setUseInnerVoice(true);
|
||||
// 计算并启动导航
|
||||
mAMapNavi.calculateDriveRoute(startNaviPoi, endNaviPoi, wayPoints, PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT);
|
||||
|
||||
Log.d(TAG, "Navigation started from: " + startNaviPoi.getName() + " - " + endNaviPoi.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Process intent error", e);
|
||||
Toast.makeText(this, "导航启动失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (naviView != null) {
|
||||
naviView.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (naviView != null) {
|
||||
naviView.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (naviView != null) {
|
||||
naviView.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 停止导航
|
||||
if (mAMapNavi != null) {
|
||||
mAMapNavi.stopNavi();
|
||||
mAMapNavi.removeAMapNaviListener(this);
|
||||
}
|
||||
if (naviView != null) {
|
||||
naviView.onDestroy();
|
||||
}
|
||||
Log.d(TAG, "NavigationActivity destroyed");
|
||||
}
|
||||
|
||||
private int dp2px(float dp) {
|
||||
float density = getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * density + 0.5f);
|
||||
}
|
||||
|
||||
// ============ AMapNaviListener 回调实现 ============
|
||||
|
||||
|
||||
@Override
|
||||
public void onInitNaviFailure() {
|
||||
Log.e(TAG, "onInitNaviFailure");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitNaviSuccess() {
|
||||
Log.d(TAG, "onInitNaviSuccess");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartNavi(int i) {
|
||||
Log.d(TAG, "onStartNavi: " + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrafficStatusUpdate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChange(AMapNaviLocation aMapNaviLocation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetNavigationText(int i, String s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetNavigationText(String s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEndEmulatorNavi() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArriveDestination() {
|
||||
Log.d(TAG, "onArriveDestination");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteFailure(int i) {
|
||||
Log.e(TAG, "onCalculateRouteFailure: " + i);
|
||||
Toast.makeText(this, "路径计算失败: " + i, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCameraInfo(AMapNaviCameraInfo[] aMapNaviCameraInfos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIntervalCameraInfo(AMapNaviCameraInfo aMapNaviCameraInfo, AMapNaviCameraInfo aMapNaviCameraInfo1, int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceAreaUpdate(AMapServiceAreaInfo[] aMapServiceAreaInfos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCross(AMapNaviCross aMapNaviCross) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideCross() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showModeCross(AMapModelCross aMapModelCross) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideModeCross() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLaneInfo(AMapLaneInfo[] aMapLaneInfos, byte[] bytes, byte[] bytes1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLaneInfo(AMapLaneInfo aMapLaneInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLaneInfo() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteSuccess(int[] ints) {
|
||||
Log.d(TAG, "onCalculateRouteSuccess");
|
||||
if (mAMapNavi != null) {
|
||||
mAMapNavi.startNavi(NaviType.GPS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyParallelRoad(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo[] aMapNaviTrafficFacilityInfos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo aMapNaviTrafficFacilityInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAimlessModeStatistics(AimLessModeStat aimLessModeStat) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAimlessModeCongestionInfo(AimLessModeCongestionInfo aimLessModeCongestionInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayRing(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteSuccess(AMapCalcRouteResult aMapCalcRouteResult) {
|
||||
Log.d(TAG, "onCalculateRouteSuccess (result)");
|
||||
if (mAMapNavi != null) {
|
||||
mAMapNavi.startNavi(NaviType.GPS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteFailure(AMapCalcRouteResult aMapCalcRouteResult) {
|
||||
Log.e(TAG, "onCalculateRouteFailure: " + aMapCalcRouteResult.getErrorCode());
|
||||
Toast.makeText(this, "路径计算失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviRouteNotify(AMapNaviRouteNotifyData aMapNaviRouteNotifyData) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsSignalWeak(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReCalculateRouteForYaw() {
|
||||
Log.d(TAG, "onReCalculateRouteForYaw");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReCalculateRouteForTrafficJam() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArrivedWayPoint(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsOpenStatus(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviInfoUpdate(NaviInfo naviInfo) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.amap.api.services.help.Inputtips;
|
||||
import com.amap.api.services.help.InputtipsQuery;
|
||||
import com.amap.api.services.help.Tip;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
|
||||
/**
|
||||
* 搜索目的地页面
|
||||
*/
|
||||
public class SearchDestinationActivity extends ComponentActivity implements Inputtips.InputtipsListener {
|
||||
private static final String TAG = "SearchDestinationActivity";
|
||||
|
||||
public static final String EXTRA_RESULT_NAME = "result_name";
|
||||
public static final String EXTRA_RESULT_ADDRESS = "result_address";
|
||||
public static final String EXTRA_RESULT_LAT = "result_lat";
|
||||
public static final String EXTRA_RESULT_LON = "result_lon";
|
||||
public static final String EXTRA_RESULT_DISTRICT = "result_district";
|
||||
|
||||
// 静态回调接口
|
||||
public interface DestinationCallback {
|
||||
void onDestinationSelected(String name, String address, double lat, double lon, String district);
|
||||
}
|
||||
|
||||
private static DestinationCallback callback;
|
||||
|
||||
public static void setCallback(DestinationCallback callback) {
|
||||
SearchDestinationActivity.callback = callback;
|
||||
}
|
||||
|
||||
private EditText searchInput;
|
||||
private ListView suggestionList;
|
||||
private ImageView backBtn;
|
||||
|
||||
private ArrayAdapter<String> suggestionAdapter;
|
||||
private List<Tip> currentTipList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
setContentView(R.layout.activity_search_destination);
|
||||
|
||||
initViews();
|
||||
setupListeners();
|
||||
|
||||
// 自动显示键盘
|
||||
showKeyboard();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in onCreate", e);
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
try {
|
||||
Log.d(TAG, "initViews started");
|
||||
searchInput = findViewById(R.id.search_input);
|
||||
suggestionList = findViewById(R.id.suggestion_list);
|
||||
backBtn = findViewById(R.id.back_btn);
|
||||
|
||||
currentTipList = new ArrayList<>();
|
||||
|
||||
Log.d(TAG, "initViews completed: searchInput=" + (searchInput != null ? "ok" : "null") + ", suggestionList=" + (suggestionList != null ? "ok" : "null") + ", backBtn=" + (backBtn != null ? "ok" : "null"));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in initViews", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
suggestionAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<>()) {
|
||||
@Override
|
||||
public View getView(int position, android.view.View convertView, android.view.ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
TextView text = view.findViewById(android.R.id.text1);
|
||||
text.setTextColor(Color.parseColor("#333333"));
|
||||
text.setTextSize(14);
|
||||
view.setBackgroundColor(Color.TRANSPARENT);
|
||||
return view;
|
||||
}
|
||||
};
|
||||
suggestionList.setAdapter(suggestionAdapter);
|
||||
suggestionList.setDivider(new ColorDrawable(Color.LTGRAY));
|
||||
suggestionList.setDividerHeight(1);
|
||||
}
|
||||
|
||||
|
||||
private void setupListeners() {
|
||||
try {
|
||||
Log.d(TAG, "setupListeners started");
|
||||
|
||||
// 返回按钮
|
||||
backBtn.setOnClickListener(v -> {
|
||||
Log.d(TAG, "Back button clicked");
|
||||
hideKeyboard();
|
||||
finish();
|
||||
});
|
||||
|
||||
// 文本变化监听
|
||||
searchInput.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
Log.d(TAG, "Text changed: " + (s != null ? s.toString() : "null") + ", length=" + (s != null ? s.length() : 0));
|
||||
if (s.length() > 1) {
|
||||
searchLocation(s.toString());
|
||||
} else {
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
|
||||
// 列表点击
|
||||
suggestionList.setOnItemClickListener((parent, view, position, id) -> {
|
||||
Log.d(TAG, "Suggestion clicked at position: " + position);
|
||||
try {
|
||||
Tip tip = currentTipList.get(position);
|
||||
if (tip.getPoint() != null) {
|
||||
String name = tip.getName();
|
||||
String address = tip.getAddress();
|
||||
String district = tip.getDistrict();
|
||||
double lat = tip.getPoint().getLatitude();
|
||||
double lon = tip.getPoint().getLongitude();
|
||||
|
||||
Log.d(TAG, "Selected destination: " + name + ", lat=" + lat + ", lon=" + lon);
|
||||
|
||||
// 优先使用静态回调
|
||||
if (callback != null) {
|
||||
callback.onDestinationSelected(name, address, lat, lon, district);
|
||||
} else {
|
||||
// 降级使用 Intent 回调
|
||||
Intent result = new Intent();
|
||||
result.putExtra(EXTRA_RESULT_NAME, name);
|
||||
result.putExtra(EXTRA_RESULT_ADDRESS, address);
|
||||
result.putExtra(EXTRA_RESULT_LAT, lat);
|
||||
result.putExtra(EXTRA_RESULT_LON, lon);
|
||||
result.putExtra(EXTRA_RESULT_DISTRICT, district);
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
|
||||
hideKeyboard();
|
||||
finish();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error handling suggestion click", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
Log.d(TAG, "setupListeners completed");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in setupListeners", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void searchLocation(String keyword) {
|
||||
try {
|
||||
Log.d(TAG, "Searching for: " + keyword);
|
||||
InputtipsQuery query = new InputtipsQuery(keyword, "");
|
||||
query.setCityLimit(false);
|
||||
Inputtips inputtips = new Inputtips(this, query);
|
||||
inputtips.setInputtipsListener(this);
|
||||
inputtips.requestInputtipsAsyn();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in searchLocation", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onGetInputtips(List<Tip> tipList, int rCode) {
|
||||
Log.d(TAG, "onGetInputtips called, rCode=" + rCode + ", tipList size=" + (tipList != null ? tipList.size() : "null"));
|
||||
if (rCode == 1000 && tipList != null && !tipList.isEmpty()) {
|
||||
currentTipList.clear();
|
||||
currentTipList.addAll(tipList);
|
||||
|
||||
List<String> suggestionNames = new ArrayList<>();
|
||||
for (Tip tip : tipList) {
|
||||
String name = tip.getName();
|
||||
String district = tip.getDistrict();
|
||||
String displayText = district != null && !district.isEmpty() ? name + " " + district : name;
|
||||
suggestionNames.add(displayText);
|
||||
}
|
||||
|
||||
runOnUiThread(() -> {
|
||||
suggestionAdapter.clear();
|
||||
suggestionAdapter.addAll(suggestionNames);
|
||||
suggestionAdapter.notifyDataSetChanged();
|
||||
|
||||
if (suggestionNames.size() > 0) {
|
||||
suggestionList.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showKeyboard() {
|
||||
searchInput.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(searchInput, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPointerCaptureChanged(boolean hasCapture) {
|
||||
super.onPointerCaptureChanged(hasCapture);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 清理回调,避免内存泄漏
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
BIN
ln_jq_app/android/app/src/main/res/drawable/back.png
Normal file
|
After Width: | Height: | Size: 705 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 背景颜色:使用浅灰色 -->
|
||||
<solid android:color="#ffffffff" />
|
||||
<!-- 圆角:8dp左右 -->
|
||||
<corners android:radius="8dp" />
|
||||
<!-- 边框:可选,如果需要更清晰的边缘可以加上 -->
|
||||
<stroke android:width="1dp" android:color="#EAEAEA" />
|
||||
</shape>
|
||||
BIN
ln_jq_app/android/app/src/main/res/drawable/car.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_close.png
Normal file
|
After Width: | Height: | Size: 880 B |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_fuel.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_location.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_marker.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_mileage.png
Normal file
|
After Width: | Height: | Size: 781 B |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_person.png
Normal file
|
After Width: | Height: | Size: 762 B |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_phone.png
Normal file
|
After Width: | Height: | Size: 865 B |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_price.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_tag.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_time.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_toll.png
Normal file
|
After Width: | Height: | Size: 791 B |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_un_marker.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#FFFFFF" />
|
||||
<corners
|
||||
android:topLeftRadius="24dp"
|
||||
android:topRightRadius="24dp"
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="0dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FFFFFF"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_btn"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:src="@drawable/back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:text="选择地点"
|
||||
android:textColor="#000000"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="12dp"
|
||||
android:background="@drawable/bg_search_input"
|
||||
android:hint="输入地址"
|
||||
android:imeOptions="actionDone"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="#333333"
|
||||
android:textColorHint="#999999"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/suggestion_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
BIN
ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
@@ -21,11 +21,9 @@
|
||||
.amap-callamap,
|
||||
.amap-lib-driving-callBtn,
|
||||
.amap-copyright,
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
.amap-logo{bottom: 60px}
|
||||
|
||||
/* 去除高德默认的 label 边框和背景 */
|
||||
/* 去除高德默认的 label 边框 and 背景 */
|
||||
.amap-marker-label {
|
||||
border: none !important;
|
||||
background-color: transparent !important;
|
||||
@@ -109,7 +107,7 @@
|
||||
/* --- 导航结果面板 (底部弹出) --- */
|
||||
#panel {
|
||||
position: fixed;
|
||||
bottom: 75px;
|
||||
bottom: 95px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 35%;
|
||||
@@ -129,7 +127,7 @@
|
||||
#location-btn {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 75px;
|
||||
bottom: 105px;
|
||||
/* 默认位置 */
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
@@ -159,7 +157,7 @@
|
||||
/* --- 调整比例尺位置 --- */
|
||||
.amap-scalecontrol {
|
||||
/* 初始状态:避开底部的定位按钮或留出安全间距 */
|
||||
bottom: 80px !important;
|
||||
bottom: 110px !important;
|
||||
left: 10px !important;
|
||||
transition: bottom 0.3s ease;
|
||||
/* 增加平滑动画 */
|
||||
@@ -195,10 +193,10 @@
|
||||
|
||||
<div id="search-box">
|
||||
<div class="input-row">
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" />
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" onfocus="this.select()" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" onfocus="this.select()" />
|
||||
<button onclick="startRouteSearch()">路径规划</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,6 +219,7 @@
|
||||
var currentLat, currentLng;
|
||||
var isTruckMode = false;
|
||||
var isInitialLocationSet = false;
|
||||
var stationMarkers = []; // 存储所有站点的标记
|
||||
|
||||
|
||||
function initMap() {
|
||||
@@ -243,6 +242,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 点击地图空白处重置状态
|
||||
map.on('click', function() {
|
||||
resetSearchState();
|
||||
});
|
||||
|
||||
// 添加基础控件
|
||||
map.addControl(new AMap.Scale());
|
||||
map.addControl(new AMap.ToolBar({
|
||||
@@ -284,6 +288,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置搜索状态,隐藏面板和路线
|
||||
*/
|
||||
function resetSearchState() {
|
||||
if (document.body.classList.contains('panel-active')) {
|
||||
console.log("JS->: 重置地图状态");
|
||||
document.body.classList.remove('panel-active');
|
||||
var panel = document.getElementById('panel');
|
||||
panel.style.display = 'none';
|
||||
if (driving) driving.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心功能 1: 接收 Flutter 传来的定位数据
|
||||
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
||||
@@ -336,6 +353,8 @@
|
||||
fetchStationInfo(addressComponent.province, addressComponent.city,
|
||||
addressComponent.district, lat, lng);
|
||||
|
||||
fetchStationInfoList(lat, lng);
|
||||
|
||||
// 策略1: 优先使用最近的、类型合适的POI的名称
|
||||
if (pois && pois.length > 0) {
|
||||
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名
|
||||
@@ -397,7 +416,6 @@
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// "asoco-token": "e28eada8-4611-4dc2-a942-0122e52f52da"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
province: province,
|
||||
@@ -437,6 +455,79 @@
|
||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点列表
|
||||
*/
|
||||
function fetchStationInfoList(lat, lng) {
|
||||
fetch('https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
longitude: lng,
|
||||
latitude: lat,
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('网络响应错误: ' + response.status);
|
||||
}
|
||||
return response.json(); // 解析 JSON
|
||||
})
|
||||
.then(res => {
|
||||
console.log("JS->:2 接口完整返回:", JSON.stringify(res));
|
||||
if (res.code === 0 && res.data && Array.isArray(res.data)) {
|
||||
// 1. 清除旧的站点标记
|
||||
stationMarkers.forEach(m => m.setMap(null));
|
||||
stationMarkers = [];
|
||||
|
||||
// 2. 循环标记所有加氢站
|
||||
res.data.forEach(station => {
|
||||
var stationIcon = new AMap.Icon({
|
||||
size: new AMap.Size(32, 32),
|
||||
image: 'ic_tag.png',
|
||||
imageSize: new AMap.Size(32, 32)
|
||||
});
|
||||
|
||||
var sMarker = new AMap.Marker({
|
||||
map: map,
|
||||
position: [station.longitude, station.latitude],
|
||||
icon: stationIcon,
|
||||
offset: new AMap.Pixel(-16, -32),
|
||||
title: station.name,
|
||||
label: {
|
||||
content: '<div class="custom-bubble">' + station.name +
|
||||
'</div>',
|
||||
direction: 'top'
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 绑定点击事件:选中即为目的地,并开始规划
|
||||
sMarker.on('click', function () {
|
||||
var stationName = station.name || "目的地";
|
||||
document.getElementById('endInput').value = station.address ||
|
||||
stationName;
|
||||
|
||||
// 更新当前的 destMarker
|
||||
if (destMarker && destMarker !== sMarker) destMarker.setMap(null);
|
||||
destMarker = sMarker;
|
||||
|
||||
// 直接传入坐标对象,避免关键字搜索失败
|
||||
var loc = new AMap.LngLat(station.longitude, station.latitude);
|
||||
startRouteSearch(loc);
|
||||
});
|
||||
|
||||
stationMarkers.push(sMarker);
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("JS->: 业务报错或无数据:", res.message);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* 地理编码并在地图标记终点
|
||||
*/
|
||||
@@ -447,7 +538,6 @@
|
||||
if (destMarker) destMarker.setMap(null);
|
||||
|
||||
// 2. 创建自定义图标
|
||||
// 假设图标大小为 32x32,你可以根据实际图片尺寸调整 Size
|
||||
var destIcon = new AMap.Icon({
|
||||
size: new AMap.Size(32, 32), // 图标尺寸
|
||||
image: 'ic_tag.png', // 本地图片路径
|
||||
@@ -459,8 +549,6 @@
|
||||
map: map,
|
||||
position: [longitude, latitude],
|
||||
icon: destIcon, // 使用自定义图标
|
||||
// 偏移量:如果图标底部中心是尖角,offset 设为宽的一半的负数,高度的负数
|
||||
// 这样能确保图片的底部尖端指向地图上的精确位置
|
||||
offset: new AMap.Pixel(-16, -32),
|
||||
title: name,
|
||||
label: {
|
||||
@@ -469,17 +557,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 打印调试信息
|
||||
console.log("JS->: 终点标记已添加", address, loc.toString());
|
||||
|
||||
// 5. 自动调整视野包含起点和终点
|
||||
// if (marker) {
|
||||
// // 如果起点标志已存在,缩放地图以展示两者
|
||||
// map.setFitView([marker, destMarker], false, [60, 60, 60, 60]);
|
||||
// } else {
|
||||
// // 如果没有起点,直接跳到终点
|
||||
// map.setCenter(loc);
|
||||
// }
|
||||
console.log("JS->: 终点标记已添加", address);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,11 +576,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 路径规划
|
||||
* @param {AMap.LngLat} [destLoc] 可选的终点坐标
|
||||
*/
|
||||
function startRouteSearch() {
|
||||
// 获取输入框的文字
|
||||
function startRouteSearch(destLoc) {
|
||||
var startKw = document.getElementById('startInput').value;
|
||||
var endKw = document.getElementById('endInput').value;
|
||||
|
||||
@@ -510,63 +589,59 @@
|
||||
alert("请输入起点");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endKw) {
|
||||
alert("请输入终点");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除旧路线
|
||||
if (driving) driving.clear();
|
||||
|
||||
// 收起键盘
|
||||
document.getElementById('startInput').blur();
|
||||
document.getElementById('endInput').blur();
|
||||
|
||||
// --- 构造路径规划的点 (使用数组方式,更灵活) ---
|
||||
var points = [];
|
||||
|
||||
// 1. 处理起点逻辑
|
||||
// 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标
|
||||
if (!startKw || startKw === '我的位置') {
|
||||
// 1. 起点逻辑
|
||||
if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
|
||||
if (!currentLng || !currentLat) {
|
||||
// 如果还没获取到定位
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('requestLocation');
|
||||
}
|
||||
if (window.flutter_inappwebview) window.flutter_inappwebview.callHandler('requestLocation');
|
||||
alert("正在获取定位,请稍后...");
|
||||
return;
|
||||
}
|
||||
// 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词)
|
||||
points.push({
|
||||
keyword: '我的位置', // 用于显示的名字
|
||||
location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标
|
||||
keyword: '我的位置',
|
||||
location: new AMap.LngLat(currentLng, currentLat)
|
||||
});
|
||||
} else {
|
||||
// 如果用户手动输入了地点 (例如 "北京南站")
|
||||
// 直接存入关键词,让高德自己去搜
|
||||
points.push({
|
||||
keyword: startKw
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 处理终点逻辑 (通常是关键词)
|
||||
// 2. 终点逻辑:如果有传入坐标,则直接使用坐标导航,成功率最高
|
||||
if (destLoc) {
|
||||
points.push({
|
||||
keyword: endKw,
|
||||
location: destLoc // 关键:使用精确坐标
|
||||
});
|
||||
} else {
|
||||
points.push({
|
||||
keyword: endKw
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 发起搜索
|
||||
// points 数组里现在是一个起点对象和一个终点对象
|
||||
driving.search(points, function (status, result) {
|
||||
if (status === 'complete') {
|
||||
console.log('JS: 规划成功');
|
||||
var panel = document.getElementById('panel');
|
||||
panel.style.display = 'block';
|
||||
document.body.classList.add('panel-active');
|
||||
} else {
|
||||
console.log('JS: 规划失败', result);
|
||||
alert("规划失败,请检查起终点名称");
|
||||
}
|
||||
// else {
|
||||
// console.error('JS: 规划失败', result);
|
||||
// // 如果坐标规划都失败了,通常是由于起终点距离过近或政策限制(如货车禁行)
|
||||
// alert("路径规划未成功,请尝试微调起终点");
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
BIN
ln_jq_app/assets/images/history_bg.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
ln_jq_app/assets/images/ic_attention@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
ln_jq_app/assets/images/ic_close@2x.png
Normal file
|
After Width: | Height: | Size: 892 B |
BIN
ln_jq_app/assets/images/ic_ex_menu@2x.png
Normal file
|
After Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 594 B |
BIN
ln_jq_app/assets/images/ic_serch@2x.png
Normal file
|
After Width: | Height: | Size: 636 B |
BIN
ln_jq_app/assets/images/ic_upload@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
ln_jq_app/assets/images/mall_bar@2x.png
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
ln_jq_app/assets/images/mall_pay_success@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
ln_jq_app/assets/images/rule_bg@2x.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
ln_jq_app/assets/images/rule_bg_1@2x.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
ln_jq_app/assets/images/tips_1@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
ln_jq_app/assets/images/tips_2@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ln_jq_app/assets/images/tips_3@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
ln_jq_app/assets/images/tips_4@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ln_jq_app/assets/images/tips_5@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 545 KiB |
51
ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK.podspec
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# Be sure to run `pod lib lint AMapNavIOSSDK.podspec' to ensure this is a
|
||||
# valid spec before submitting.
|
||||
#
|
||||
# Any lines starting with a # are optional, but their use is encouraged
|
||||
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
|
||||
#
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'AMapNavIOSSDK'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'A short description of AMapNavIOSSDK.'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
# * Try to keep it short, snappy and to the point.
|
||||
# * Write the description between the DESC delimiters below.
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
|
||||
s.description = <<-DESC
|
||||
TODO: Add long description of the pod here.
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/xiaoshuai/AMapNavIOSSDK'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'xiaoshuai' => 'xiaoshuai@net.cn' }
|
||||
s.source = { :git => 'https://github.com/xiaoshuai/AMapNavIOSSDK.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
|
||||
s.ios.deployment_target = '12.0'
|
||||
|
||||
s.source_files = 'AMapNavIOSSDK/Classes/**/*'
|
||||
s.resource = 'AMapNavIOSSDK/**/*.bundle'
|
||||
s.resource_bundles = {
|
||||
'AMapNavIOSSDKPrivacyInfo' => ['AMapNavIOSSDK/**/PrivacyInfo.xcprivacy']
|
||||
}
|
||||
|
||||
# s.public_header_files = 'Pod/Classes/**/*.h'
|
||||
# s.frameworks = 'UIKit', 'MapKit'
|
||||
# s.dependency 'AFNetworking', '~> 2.3'
|
||||
|
||||
s.dependency 'Masonry'
|
||||
s.dependency 'MJExtension'
|
||||
|
||||
s.dependency 'AMapNavi-NO-IDFA'
|
||||
s.dependency 'AMapLocation-NO-IDFA'
|
||||
s.dependency 'AMapSearch-NO-IDFA'
|
||||
s.dependency 'MBProgressHUD'
|
||||
|
||||
end
|
||||
|
After Width: | Height: | Size: 966 B |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 844 B |
|
After Width: | Height: | Size: 792 B |
|
After Width: | Height: | Size: 909 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 840 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>0A2A.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// AAddHPopView.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* 气泡弹框视图
|
||||
*
|
||||
* 用法:
|
||||
* AAddHPopView *pop = [[AAddHPopView alloc] init];
|
||||
* [pop showInView:self.view sourceView:addHbtn];
|
||||
*
|
||||
* - 点击气泡外部自动消失
|
||||
* - 箭头在右下角45度,指向 sourceView 中心
|
||||
*/
|
||||
@interface AAddHPopView : UIView
|
||||
|
||||
/// 展示气泡,sourceView 为箭头指向的锚定按钮(坐标系属于 view)
|
||||
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView;
|
||||
|
||||
/// 手动消失(点击空白处会自动调用)
|
||||
- (void)dismiss;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// AAddHPopView.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
|
||||
#import "AAddHPopView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "UIColor+ANavMap.h"
|
||||
|
||||
// ─── 尺寸常量 ────────────────────────────────────────────────────────────────
|
||||
static const CGFloat kBubbleWidth = 200.f; // 气泡宽度
|
||||
static const CGFloat kHeaderHeight = 50.f; // 顶部绿色标题区高度
|
||||
static const CGFloat kOptionHeight = 60.f; // 每行选项高度
|
||||
static const CGFloat kArrowWidth = 50.f; // 箭头底边宽(aw = ah*2 使左右45度对称)
|
||||
static const CGFloat kArrowHeight = 15.f; // 箭头高度
|
||||
static const CGFloat kBubbleRadius = 15.f; // 气泡圆角半径
|
||||
static const CGFloat kBubbleGap = 5.f; // 气泡底部与 sourceView 顶部间距
|
||||
static const CGFloat kScreenPadding = 15.f; // 气泡距屏幕边缘最小距离
|
||||
|
||||
|
||||
// ─── 自定义气泡容器:自绘白色气泡 + 底部箭头 ─────────────────────────────────
|
||||
@interface _ABubbleContainerView : UIView
|
||||
/// 箭头中心 X(相对于本 view 坐标系),用于 showInView 精确定位
|
||||
@property (nonatomic, assign) CGFloat arrowCenterX;
|
||||
@end
|
||||
|
||||
@implementation _ABubbleContainerView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_arrowCenterX = kBubbleWidth / 2.f;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setArrowCenterX:(CGFloat)arrowCenterX {
|
||||
_arrowCenterX = arrowCenterX;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGFloat bw = kBubbleWidth; // 160
|
||||
CGFloat bh = kHeaderHeight + kOptionHeight * 2.f; // 150
|
||||
CGFloat r = kBubbleRadius; // 10
|
||||
CGFloat aw = kArrowWidth; // 16 箭头底边宽
|
||||
CGFloat ah = kArrowHeight; // 10 箭头高度
|
||||
|
||||
// 箭头在底部偏右:指向右下45度
|
||||
// 箭头底边: 从 (arrowX-aw/2, bh) 到 (arrowX, bh)
|
||||
// 箭头尖端: (arrowX + ah, bh + ah) — 相对于底边中心向右下45度
|
||||
CGFloat arrowX = bw - 15.f; // 箭头底边右端 X(偏右下)
|
||||
CGFloat arrowTipX = arrowX + ah; // 箭头尖端 X(向右偏移ah = 45度)
|
||||
CGFloat arrowTipY = bh + ah; // 箭头尖端 Y(向下偏移ah = 45度)
|
||||
|
||||
UIBezierPath *path = [UIBezierPath bezierPath];
|
||||
|
||||
// ── 顶部左圆角 → 顶部右圆角 ──
|
||||
[path moveToPoint:CGPointMake(r, 0)];
|
||||
[path addLineToPoint:CGPointMake(bw - r, 0)];
|
||||
[path addArcWithCenter:CGPointMake(bw - r, r) radius:r
|
||||
startAngle:-M_PI_2 endAngle:0 clockwise:YES];
|
||||
|
||||
// ── 右边 → 右下圆角 ──
|
||||
[path addLineToPoint:CGPointMake(bw, bh - r)];
|
||||
[path addArcWithCenter:CGPointMake(bw - r, bh - r) radius:r
|
||||
startAngle:0 endAngle:M_PI_2 clockwise:YES];
|
||||
|
||||
// ── 底边 → 箭头底边右端 ──
|
||||
[path addLineToPoint:CGPointMake(arrowX, bh)];
|
||||
// 箭头斜边(45度向右下到尖端)
|
||||
[path addLineToPoint:CGPointMake(arrowTipX, arrowTipY)];
|
||||
// 箭头底边左端(垂直向上)
|
||||
[path addLineToPoint:CGPointMake(arrowX - ah, bh)];
|
||||
|
||||
// ── 左下圆角 ──
|
||||
[path addArcWithCenter:CGPointMake(r, bh - r) radius:r
|
||||
startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
|
||||
|
||||
// ── 左边 → 左上圆角 ──
|
||||
[path addLineToPoint:CGPointMake(0, r)];
|
||||
[path addArcWithCenter:CGPointMake(r, r) radius:r
|
||||
startAngle:M_PI endAngle:-M_PI_2 clockwise:YES];
|
||||
[path closePath];
|
||||
|
||||
// ── 阴影 + 白色填充 ──
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(ctx);
|
||||
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 3), 10.f,
|
||||
[[UIColor blackColor] colorWithAlphaComponent:0.15f].CGColor);
|
||||
[[UIColor whiteColor] setFill];
|
||||
[path fill];
|
||||
CGContextRestoreGState(ctx);
|
||||
[[UIColor whiteColor] setFill];
|
||||
[path fill];
|
||||
|
||||
// 保存箭头尖端坐标用于定位
|
||||
_arrowTipX = arrowTipX;
|
||||
_arrowTipY = arrowTipY;
|
||||
}
|
||||
|
||||
// 供外部访问的箭头尖端坐标
|
||||
static CGFloat _arrowTipX = 0;
|
||||
static CGFloat _arrowTipY = 0;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// ─── AAddHPopView ────────────────────────────────────────────────────────────
|
||||
|
||||
@interface AAddHPopView ()
|
||||
|
||||
@property (nonatomic, strong) _ABubbleContainerView *bubbleContainer;
|
||||
@property (nonatomic, strong) UIView *headerView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UIView *separatorLine1;
|
||||
@property (nonatomic, strong) UILabel *option1Label;
|
||||
@property (nonatomic, strong) UIView *separatorLine2;
|
||||
@property (nonatomic, strong) UILabel *option2Label;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AAddHPopView
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithFrame:CGRectZero];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self p_setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Setup UI
|
||||
|
||||
- (void)p_setupUI {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// ── 点击空白处(self)消失 ──
|
||||
UITapGestureRecognizer *bgTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(dismiss)];
|
||||
bgTap.cancelsTouchesInView = NO;
|
||||
[self addGestureRecognizer:bgTap];
|
||||
|
||||
// ── 气泡容器 ──
|
||||
[self addSubview:self.bubbleContainer];
|
||||
|
||||
// ── header(绿色标题区,仅裁剪上两角) ──
|
||||
[self.bubbleContainer addSubview:self.headerView];
|
||||
[self.headerView addSubview:self.titleLabel];
|
||||
|
||||
// ── 分隔线 1 ──
|
||||
[self.bubbleContainer addSubview:self.separatorLine1];
|
||||
|
||||
// ── 选项 1 ──
|
||||
[self.bubbleContainer addSubview:self.option1Label];
|
||||
|
||||
// ── 分隔线 2 ──
|
||||
[self.bubbleContainer addSubview:self.separatorLine2];
|
||||
|
||||
// ── 选项 2 ──
|
||||
[self.bubbleContainer addSubview:self.option2Label];
|
||||
|
||||
// ── 阻止气泡内点击冒泡到 bgTap ──
|
||||
UITapGestureRecognizer *bubbleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(p_bubbleTapped)];
|
||||
[self.bubbleContainer addGestureRecognizer:bubbleTap];
|
||||
|
||||
// ── Masonry 内部约束(基于 bubbleContainer) ──
|
||||
// 子视图宽度统一约束到 kBubbleWidth(160),气泡主体实际宽度
|
||||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.equalTo(self.bubbleContainer);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(kHeaderHeight);
|
||||
}];
|
||||
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.headerView);
|
||||
}];
|
||||
|
||||
[self.separatorLine1 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.headerView.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
[self.option1Label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separatorLine1.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(kOptionHeight);
|
||||
}];
|
||||
|
||||
[self.separatorLine2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.option1Label.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
[self.option2Label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separatorLine2.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(kOptionHeight);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Show / Dismiss
|
||||
|
||||
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView {
|
||||
self.frame = view.bounds;
|
||||
[view addSubview:self];
|
||||
|
||||
// ── 计算 sourceView 在 view 坐标系中的 frame ──
|
||||
CGRect srcFrame = [sourceView convertRect:sourceView.bounds toView:view];
|
||||
|
||||
// ── 容器尺寸 ──
|
||||
CGFloat containerW = kBubbleWidth; // 160
|
||||
CGFloat containerH = kHeaderHeight + kOptionHeight * 2.f; // 150
|
||||
CGFloat ah = kArrowHeight; // 16 箭头高度
|
||||
|
||||
// ── 箭头尖端坐标(相对于 bubbleContainer 左下角为原点)──
|
||||
// 与 drawRect 中一致:arrowX = bw - 35, arrowTipX = arrowX + ah
|
||||
CGFloat arrowX_inContainer = containerW - 25.f; // 箭头底边右端 X
|
||||
CGFloat arrowTipX_inContainer = arrowX_inContainer + ah; // 箭头尖端 X(右下45度)
|
||||
CGFloat arrowTipY_inContainer = containerH + ah; // 箭头尖端 Y
|
||||
|
||||
// ── 位置计算:气泡在按钮左上方,箭头尖端指向按钮中心 ──
|
||||
CGFloat srcCenterX = CGRectGetMidX(srcFrame);
|
||||
CGFloat srcCenterY = CGRectGetMidY(srcFrame);
|
||||
|
||||
// 箭头尖端应指向按钮中心
|
||||
CGFloat containerX = srcCenterX - arrowTipX_inContainer;
|
||||
containerX = MAX(kScreenPadding, MIN(containerX, view.bounds.size.width - containerW - kScreenPadding));
|
||||
|
||||
// 气泡顶部位置
|
||||
CGFloat containerY = srcCenterY - arrowTipY_inContainer;
|
||||
containerY = MAX(kScreenPadding, containerY);
|
||||
|
||||
self.bubbleContainer.bounds = CGRectMake(0, 0, containerW, containerH + ah);
|
||||
|
||||
// 锚点设置在箭头尖端位置(相对于容器),实现箭头精准指向
|
||||
self.bubbleContainer.layer.anchorPoint = CGPointMake(arrowTipX_inContainer / containerW,
|
||||
arrowTipY_inContainer / (containerH + ah));
|
||||
self.bubbleContainer.center = CGPointMake(srcCenterX - 25, srcCenterY - 20);
|
||||
|
||||
// ── 入场动画 ──
|
||||
self.alpha = 0.f;
|
||||
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
|
||||
|
||||
[UIView animateWithDuration:0.25f
|
||||
delay:0.f
|
||||
usingSpringWithDamping:0.72f
|
||||
initialSpringVelocity:0.3f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
self.alpha = 1.f;
|
||||
self.bubbleContainer.transform = CGAffineTransformIdentity;
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)dismiss {
|
||||
[UIView animateWithDuration:0.18f
|
||||
delay:0.f
|
||||
options:UIViewAnimationOptionCurveEaseIn
|
||||
animations:^{
|
||||
self.alpha = 0.f;
|
||||
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
|
||||
} completion:^(BOOL finished) {
|
||||
[self removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private Actions
|
||||
|
||||
- (void)p_bubbleTapped {
|
||||
// 阻止冒泡,气泡内点击不消失
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (_ABubbleContainerView *)bubbleContainer {
|
||||
if (!_bubbleContainer) {
|
||||
_bubbleContainer = [[_ABubbleContainerView alloc] init];
|
||||
}
|
||||
return _bubbleContainer;
|
||||
}
|
||||
|
||||
- (UIView *)headerView {
|
||||
if (!_headerView) {
|
||||
_headerView = [[UIView alloc] init];
|
||||
_headerView.backgroundColor = [UIColor hp_colorWithRGBHex:0x1BA855];
|
||||
if (@available(iOS 11.0, *)) {
|
||||
_headerView.layer.cornerRadius = kBubbleRadius;
|
||||
_headerView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
|
||||
_headerView.clipsToBounds = YES;
|
||||
}
|
||||
}
|
||||
return _headerView;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"加氢规划模式";
|
||||
_titleLabel.textColor = [UIColor whiteColor];
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:15.f];
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIView *)separatorLine1 {
|
||||
if (!_separatorLine1) {
|
||||
_separatorLine1 = [[UIView alloc] init];
|
||||
_separatorLine1.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
|
||||
}
|
||||
return _separatorLine1;
|
||||
}
|
||||
|
||||
- (UILabel *)option1Label {
|
||||
if (!_option1Label) {
|
||||
_option1Label = [[UILabel alloc] init];
|
||||
_option1Label.text = @"送货规划模式";
|
||||
_option1Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
|
||||
_option1Label.font = [UIFont boldSystemFontOfSize:14.f];
|
||||
_option1Label.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _option1Label;
|
||||
}
|
||||
|
||||
- (UIView *)separatorLine2 {
|
||||
if (!_separatorLine2) {
|
||||
_separatorLine2 = [[UIView alloc] init];
|
||||
_separatorLine2.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
|
||||
}
|
||||
return _separatorLine2;
|
||||
}
|
||||
|
||||
- (UILabel *)option2Label {
|
||||
if (!_option2Label) {
|
||||
_option2Label = [[UILabel alloc] init];
|
||||
_option2Label.text = @"成本计算模式";
|
||||
_option2Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
|
||||
_option2Label.font = [UIFont boldSystemFontOfSize:14.f];
|
||||
_option2Label.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _option2Label;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// ABaseViewController.h
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/5.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
#define kRoutePlanBarHeight (self.navigationController.navigationBar.frame.size.height + UIApplication.sharedApplication.statusBarFrame.size.height + 0)
|
||||
|
||||
#define kRoutePlanStatusBarHeight (UIApplication.sharedApplication.statusBarFrame.size.height + 0)
|
||||
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ABaseViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ABaseViewController.m
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/5.
|
||||
//
|
||||
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
@interface ABaseViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ABaseViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
/*
|
||||
#pragma mark - Navigation
|
||||
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
// Get the new view controller using [segue destinationViewController].
|
||||
// Pass the selected object to the new view controller.
|
||||
}
|
||||
*/
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// ACustomAnnotationView.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/17.
|
||||
//
|
||||
|
||||
#import <AMapNaviKit/AMapNaviKit.h>
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ACustomAnnotationView : MAAnnotationView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// ACustomAnnotationView.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/17.
|
||||
//
|
||||
|
||||
#import "ACustomAnnotationView.h"
|
||||
|
||||
// ─── 布局常量 ─────────────────────────────────────────────────────────────────
|
||||
static const CGFloat kIconSize = 30; // 图标宽高
|
||||
static const CGFloat kIconTextGap = 6.0; // 图标与文字间距
|
||||
static const CGFloat kMaxTextWidth = 100.0; // 文字区域最大宽度
|
||||
static const CGFloat kMaxTextLines = 2; // 最多行数
|
||||
static const CGFloat kVPad = 4.0; // 整体上下内边距(用于 centerOffset 微调)
|
||||
|
||||
@interface ACustomAnnotationView ()
|
||||
|
||||
@property (nonatomic, strong) UIImageView *iconView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
/// 当前是否选中(用于更新图标 & 文字样式)
|
||||
@property (nonatomic, assign) BOOL isAnnotationSelected;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ACustomAnnotationView
|
||||
|
||||
#pragma mark - Init / Reuse
|
||||
|
||||
- (instancetype)initWithAnnotation:(id<MAAnnotation>)annotation
|
||||
reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
// 关闭 MAAnnotationView 自带的 image 渲染,避免干扰
|
||||
self.image = nil;
|
||||
[self _buildSubviews];
|
||||
[self _updateFromAnnotation];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setAnnotation:(id<MAAnnotation>)annotation {
|
||||
[super setAnnotation:annotation];
|
||||
[self _updateFromAnnotation];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Build
|
||||
|
||||
- (void)_buildSubviews {
|
||||
// ── 图标 ──────────────────────────────────────────────────
|
||||
if (!self.iconView) {
|
||||
UIImageView *iv = [[UIImageView alloc] init];
|
||||
iv.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self addSubview:iv];
|
||||
self.iconView = iv;
|
||||
}
|
||||
|
||||
// ── 文字 ──────────────────────────────────────────────────
|
||||
if (!self.titleLabel) {
|
||||
UILabel *lbl = [[UILabel alloc] init];
|
||||
lbl.numberOfLines = kMaxTextLines;
|
||||
lbl.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
lbl.textAlignment = NSTextAlignmentLeft;
|
||||
[self addSubview:lbl];
|
||||
self.titleLabel = lbl;
|
||||
}
|
||||
|
||||
// 初始化为默认(未选中)样式
|
||||
[self _applyStyle:NO];
|
||||
}
|
||||
|
||||
/// 根据选中状态切换图标 & 文字样式
|
||||
- (void)_applyStyle:(BOOL)selected {
|
||||
if (selected) {
|
||||
self.iconView.image = [AMapNavCommonUtil imageWithName3x:@"station_select_icon"];
|
||||
self.titleLabel.font = [UIFont boldSystemFontOfSize:14];
|
||||
self.titleLabel.textColor = [UIColor colorWithWhite:0.12 alpha:1.0];
|
||||
} else {
|
||||
self.iconView.image = [AMapNavCommonUtil imageWithName3x:@"station_normal_icon"];
|
||||
self.titleLabel.font = [UIFont systemFontOfSize:13];
|
||||
self.titleLabel.textColor = [UIColor colorWithWhite:0.25 alpha:1.0];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)_updateFromAnnotation {
|
||||
NSString *text = nil;
|
||||
id<MAAnnotation> ann = self.annotation;
|
||||
if ([ann respondsToSelector:@selector(title)]) {
|
||||
text = ann.title;
|
||||
}
|
||||
if (text.length == 0) {
|
||||
text = @"加氢站";
|
||||
}
|
||||
self.titleLabel.text = text;
|
||||
}
|
||||
|
||||
#pragma mark - Selection(MAAnnotationView 官方选中回调)
|
||||
|
||||
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
|
||||
[super setSelected:selected animated:animated];
|
||||
self.isAnnotationSelected = selected;
|
||||
[self _applyStyle:selected];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
// 文字最大 100pt、最多 2 行
|
||||
CGSize textConstraint = CGSizeMake(kMaxTextWidth,
|
||||
CGFLOAT_MAX);
|
||||
CGSize textFit = [self.titleLabel sizeThatFits:textConstraint];
|
||||
// 高度上限:2 行
|
||||
CGFloat lineH = self.titleLabel.font.lineHeight;
|
||||
CGFloat maxTextH = lineH * kMaxTextLines
|
||||
+ self.titleLabel.font.leading * (kMaxTextLines - 1);
|
||||
CGFloat textW = MIN(textFit.width, kMaxTextWidth);
|
||||
CGFloat textH = MIN(textFit.height, maxTextH);
|
||||
|
||||
CGFloat totalW = kIconSize + kIconTextGap + textW;
|
||||
CGFloat totalH = MAX(kIconSize, textH) + kVPad * 2;
|
||||
|
||||
// 更新自身 bounds
|
||||
self.bounds = CGRectMake(0, 0, totalW, totalH);
|
||||
|
||||
// 图标:垂直居中
|
||||
CGFloat iconY = (totalH - kIconSize) * 0.5;
|
||||
self.iconView.frame = CGRectMake(0, iconY, kIconSize, kIconSize);
|
||||
|
||||
// 文字:垂直居中
|
||||
CGFloat textY = (totalH - textH) * 0.5;
|
||||
self.titleLabel.frame = CGRectMake(kIconSize + 1, textY, textW, textH);
|
||||
|
||||
// 锚点:让图标底部对齐地图坐标点
|
||||
// MAAnnotationView 的 centerOffset 以 (0,0)=中心 为原点
|
||||
// 默认选中图标底部对准坐标:向上偏移 totalH/2
|
||||
self.centerOffset = CGPointMake(totalW / 2.0 - kIconSize / 2.0,
|
||||
-(totalH / 2.0));
|
||||
}
|
||||
|
||||
#pragma mark - Hit Test
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
UIView *hit = [super hitTest:point withEvent:event];
|
||||
if (hit == self || [hit isDescendantOfView:self]) {
|
||||
return self;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
#pragma mark - Touch
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
|
||||
id<MAAnnotation> annotation = self.annotation;
|
||||
if (!annotation) return;
|
||||
|
||||
UIView *sv = self.superview;
|
||||
while (sv && ![sv isKindOfClass:[MAMapView class]]) {
|
||||
sv = sv.superview;
|
||||
}
|
||||
MAMapView *mapView = (MAMapView *)sv;
|
||||
if (!mapView) return;
|
||||
|
||||
// [mapView deselectAnnotation:annotation animated:NO];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ACustomNaviDriveController.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/4/11.
|
||||
//
|
||||
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ACustomNaviDriveController : ABaseViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ACustomNaviDriveController.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/4/11.
|
||||
//
|
||||
|
||||
#import "ACustomNaviDriveController.h"
|
||||
|
||||
@interface ACustomNaviDriveController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ACustomNaviDriveController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// AMapNavSDKHeader.h
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/2/10.
|
||||
//
|
||||
|
||||
#ifndef AMapNavSDKHeader_h
|
||||
#define AMapNavSDKHeader_h
|
||||
|
||||
//#define kAMapSDKDebugFlag
|
||||
// iPhone X
|
||||
#define AMP_iPhoneX (UIApplication.sharedApplication.keyWindow.safeAreaInsets.bottom > 0 ? YES : NO)
|
||||
|
||||
// Status bar height.
|
||||
#define AMP_StatusBarHeight (AMP_iPhoneX ? 44.f : 20.f)
|
||||
|
||||
// Navigation bar height.
|
||||
#define AMP_NavigationBarHeight 44.f
|
||||
|
||||
// Tabbar height.
|
||||
#define AMP_TabbarHeight (AMP_iPhoneX ? (49.f+34.f + 5) : 49.f)
|
||||
|
||||
// Tabbar safe bottom margin.
|
||||
#define AMP_TabbarSafeBottomMargin (AMP_iPhoneX ? 34.f : 0.f)
|
||||
|
||||
|
||||
#pragma mark - url
|
||||
///获取站点列表
|
||||
#define kGetStationListUrl @"https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation"
|
||||
|
||||
///单个站点详情
|
||||
#define kGetStationDetailtUrl @"https://beta-esg.api.lnh2e.com/appointment/station/getStationInfoByArea"
|
||||
|
||||
|
||||
///获取途经点
|
||||
/**
|
||||
请求方式:post 暂时调不通
|
||||
{
|
||||
"longitude":"121.254139",
|
||||
"latitude":"31.214628",
|
||||
"plateNumber":"浙F32111F",
|
||||
"hydrogenSiteId":""//加氢站DI}
|
||||
|
||||
*/
|
||||
#define kGetRoutePointtUrl @"https://beta-esg.api.lnh2e.com/appointment/truck/truckRouteAlgorithm"
|
||||
|
||||
|
||||
#import "ANavPointModel.h"
|
||||
|
||||
#endif /* AMapNavSDKHeader_h */
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// AMapNavSDKManager.h
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/2/10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARoutePlaneController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AMapNavSDKManager : NSObject
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
@property (nonatomic , strong) NSString * localCity;
|
||||
@property (nonatomic, copy) NSString * locationAddressDetail;
|
||||
|
||||
@property (nonatomic , strong , readonly) UIViewController * targetVC;
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
- (void)configWithKey:(NSString*)key;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// AMapNavSDKManager.m
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/2/10.
|
||||
//
|
||||
|
||||
#import "AMapNavSDKManager.h"
|
||||
#import "AMapPrivacyUtility.h"
|
||||
|
||||
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||||
|
||||
@interface AMapNavSDKManager ()
|
||||
@property (nonatomic , strong , readwrite) UIViewController * targetVC;
|
||||
@end
|
||||
|
||||
@implementation AMapNavSDKManager
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static AMapNavSDKManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[AMapNavSDKManager alloc] init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_targetVC = [ARoutePlaneController new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)configWithKey:(NSString*)key {
|
||||
if (1) {
|
||||
/*
|
||||
* 调用隐私合规处理方法
|
||||
*/
|
||||
// [AMapPrivacyUtility handlePrivacyAgreeStatusIn:_targetVC];
|
||||
//
|
||||
// 初始化高德导航SDK
|
||||
[self configureAPIKey:key];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (UIViewController *)targetVC {
|
||||
return _targetVC;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
- (void)configureAPIKey:(NSString*)key {
|
||||
if ([key length] == 0)
|
||||
{
|
||||
NSString *reason = [NSString stringWithFormat:@"apiKey为空,请检查key是否正确设置。"];
|
||||
|
||||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:reason delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
|
||||
|
||||
[alert show];
|
||||
}
|
||||
[AMapServices sharedServices].enableHTTPS = YES;
|
||||
[AMapServices sharedServices].apiKey = (NSString *)key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ARoutePlaneController.h
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/5.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
#import "SelectableOverlay.h"
|
||||
#import "NaviPointAnnotation.h"
|
||||
|
||||
#import <AMapNaviKit/AMapNaviKit.h>
|
||||
|
||||
#import "ACustomAnnotationView.h"
|
||||
#import "AMapNavSDKHeader.h"
|
||||
#import "ACustomPointAnnotation.h"
|
||||
#import "ATripCalcResponse.h"
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ARoutePlaneController : ABaseViewController<AMapNaviDriveDataRepresentable>
|
||||
@property (nonatomic , strong) AMapNaviDriveView * driveView;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ASearchAddressController.h
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/6.
|
||||
//
|
||||
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
#import <AMapSearchKit/AMapSearchKit.h>
|
||||
#import <AMapLocationKit/AMapLocationKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASearchAddressController : ABaseViewController
|
||||
|
||||
@property (nonatomic , copy) void(^selectAddressBlk)(AMapPOI * poi);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,274 @@
|
||||
//
|
||||
// ASearchAddressController.m
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/6.
|
||||
//
|
||||
|
||||
#import "ASearchAddressController.h"
|
||||
#import "AMapNavSDKManager.h"
|
||||
|
||||
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||||
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
@interface ASearchAddressController ()<UITextFieldDelegate , AMapSearchDelegate,UITableViewDelegate , UITableViewDataSource>
|
||||
|
||||
@property (nonatomic , strong) UITableView *tableView;
|
||||
@property (nonatomic , strong) UIBarButtonItem *rightItem;
|
||||
@property (nonatomic ,strong)UIButton * backBtn;
|
||||
|
||||
@property (nonatomic , strong) NSMutableArray *dataArr;
|
||||
|
||||
@property (nonatomic, strong) UITextField *inputAddressTf;
|
||||
@property (nonatomic, strong) AMapSearchAPI *search;
|
||||
@property (nonatomic,assign)NSInteger currPage;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASearchAddressController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.title = @"选择地点";
|
||||
self.currPage = 1;
|
||||
_dataArr = [NSMutableArray array];
|
||||
|
||||
|
||||
[self initSubview];
|
||||
|
||||
self.search = [[AMapSearchAPI alloc] init];
|
||||
self.search.delegate = self;
|
||||
|
||||
#ifdef kAMapSDKDebugFlag
|
||||
self.inputAddressTf.text = @"人民广场";
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.inputAddressTf becomeFirstResponder];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
-(void)initSubview {
|
||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:self.backBtn];
|
||||
|
||||
UITextField * inputAddressTf = [[UITextField alloc] init];
|
||||
inputAddressTf.borderStyle = UITextBorderStyleRoundedRect;
|
||||
inputAddressTf.placeholder = @"输入地址";
|
||||
inputAddressTf.returnKeyType = UIReturnKeySearch;
|
||||
inputAddressTf.tag = 100;
|
||||
inputAddressTf.delegate = self;
|
||||
|
||||
[self.view addSubview:inputAddressTf];
|
||||
|
||||
[inputAddressTf mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.mas_equalTo(self.view).offset(10);
|
||||
make.top.mas_equalTo(self.view).offset(kRoutePlanBarHeight + 10);
|
||||
make.height.mas_equalTo(@30);
|
||||
}];
|
||||
|
||||
self.inputAddressTf = inputAddressTf;
|
||||
|
||||
|
||||
|
||||
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[btn setTitle:@"当前位置" forState:UIControlStateNormal];
|
||||
btn.backgroundColor = [UIColor whiteColor];
|
||||
btn.layer.borderColor = [UIColor blueColor].CGColor;
|
||||
btn.layer.borderWidth = 1;
|
||||
btn.layer.cornerRadius = 5;
|
||||
btn.titleLabel.font = [UIFont systemFontOfSize:12];
|
||||
btn.hidden = YES;
|
||||
|
||||
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
|
||||
[btn addTarget:self action:@selector(searchBtnAction) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:btn];
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(inputAddressTf.mas_right).offset(12);
|
||||
make.top.mas_equalTo(inputAddressTf);
|
||||
make.right.mas_equalTo(self.view).offset(-10);
|
||||
make.height.mas_equalTo(@30);
|
||||
make.width.mas_equalTo(70);
|
||||
}];
|
||||
|
||||
|
||||
[self.view addSubview:self.tableView];
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(inputAddressTf.mas_bottom).offset(5);
|
||||
make.left.equalTo(self.view);
|
||||
make.bottom.equalTo(self.view);
|
||||
make.centerX.equalTo(self.view);
|
||||
|
||||
}];
|
||||
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
|
||||
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
AMapPOI * m = self.dataArr[indexPath.row];
|
||||
cell.textLabel.text = [NSString stringWithFormat:@"%@" , m.name ];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.dataArr.count;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
AMapPOI * m = self.dataArr[indexPath.row];
|
||||
|
||||
if (self.selectAddressBlk) {
|
||||
self.selectAddressBlk(m);
|
||||
}
|
||||
|
||||
[self backBtnAction];
|
||||
// [self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row == self.dataArr.count - 1 && (self.dataArr.count % 20 == 0)) {
|
||||
self.currPage = self.currPage + 1;
|
||||
[self requestAddressWithAddress:self.inputAddressTf.text atPage:self.currPage];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
}
|
||||
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
-(UIButton *)backBtn{
|
||||
if (!_backBtn) {
|
||||
_backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_backBtn.frame = CGRectMake(0, 0, 40, 30);
|
||||
_backBtn.imageEdgeInsets = UIEdgeInsetsMake(2, -5, 2, 5);
|
||||
|
||||
// _backBtn.backgroundColor = UIColor.redColor;
|
||||
[_backBtn setImage:[AMapNavCommonUtil imageWithName:@"icon_fanhui"] forState:UIControlStateNormal];
|
||||
[_backBtn addTarget:self action:@selector(backBtnAction) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _backBtn;
|
||||
}
|
||||
|
||||
-(void)backBtnAction {
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Action
|
||||
-(void)searchBtnAction {
|
||||
AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager];
|
||||
self.inputAddressTf.text = sdk.locationAddressDetail;
|
||||
|
||||
[self startSearchWithAddress:self.inputAddressTf.text];
|
||||
}
|
||||
|
||||
-(void)startSearchWithAddress:(NSString *)addr {
|
||||
if (!addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
|
||||
|
||||
request.keywords = addr;
|
||||
|
||||
AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager];
|
||||
request.city = sdk.localCity;
|
||||
|
||||
|
||||
// request.types = @"高等院校";
|
||||
// request.requireExtension = YES;
|
||||
request.offset =20;
|
||||
request.page = 1;
|
||||
|
||||
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
|
||||
request.cityLimit = YES;
|
||||
// request.requireSubPOIs = YES;
|
||||
|
||||
|
||||
[self.search AMapPOIKeywordsSearch:request];
|
||||
|
||||
}
|
||||
|
||||
-(void)requestAddressWithAddress:(NSString *)addr atPage:(NSInteger)page {
|
||||
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
|
||||
|
||||
request.keywords = addr;
|
||||
|
||||
AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager];
|
||||
request.city = sdk.localCity;
|
||||
|
||||
|
||||
// request.types = @"高等院校";
|
||||
// request.requireExtension = YES;
|
||||
request.offset =20;
|
||||
request.page = page;
|
||||
|
||||
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
|
||||
request.cityLimit = YES;
|
||||
// request.requireSubPOIs = YES;
|
||||
|
||||
|
||||
[self.search AMapPOIKeywordsSearch:request];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
/* POI 搜索回调. */
|
||||
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response
|
||||
{
|
||||
NSArray * pois = response.pois;
|
||||
if (pois.count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//解析response获取POI信息,具体解析见 Demo
|
||||
|
||||
[self.dataArr addObjectsFromArray:pois];
|
||||
|
||||
[self.tableView reloadData];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
[textField resignFirstResponder];
|
||||
[self.dataArr removeAllObjects];
|
||||
self.currPage = 1;
|
||||
|
||||
[self startSearchWithAddress:textField.text];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[self.inputAddressTf resignFirstResponder];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// AStationDetailPopupController.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/22.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ANavPointModel.h"
|
||||
#import "AMapNavSDKHeader.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class AStationDetailPopupController;
|
||||
|
||||
@protocol AStationDetailPopupDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/// 点击"开始导航"
|
||||
- (void)stationDetailPopupDidTapStartNavi:(AStationDetailPopupController *)popup;
|
||||
/// 点击关闭
|
||||
- (void)stationDetailPopupDidTapClose:(AStationDetailPopupController *)popup;
|
||||
|
||||
@end
|
||||
|
||||
@interface AStationDetailPopupController : UIViewController
|
||||
|
||||
@property (nonatomic, strong, nullable) ANavPointModel *pointModel;
|
||||
|
||||
/// 预计加氢费用(元),可由外部传入;若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *estimatedCost;
|
||||
|
||||
/// 预计时间,如 @"15分钟";若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *estimatedTime;
|
||||
|
||||
/// 行驶里程,如 @"23.5公里";若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *driveDistance;
|
||||
|
||||
/// 过路费,如 @"30元";若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *tollFee;
|
||||
|
||||
/// 营业时间,如 @"00:00-24:00";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *businessHours;
|
||||
|
||||
/// 站点联系人,如 @"陈凯";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *contactName;
|
||||
|
||||
/// 联系方式,如 @"18019187371";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *contactPhone;
|
||||
|
||||
/// 加氢价格,如 @"32元/L";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenPrice;
|
||||
|
||||
|
||||
@property (nonatomic, weak, nullable) id<AStationDetailPopupDelegate> delegate;
|
||||
|
||||
/// 以半透明蒙层方式弹出在目标控制器上
|
||||
- (void)presentInViewController:(UIViewController *)parentVC;
|
||||
|
||||
/// 关闭弹框
|
||||
- (void)dismiss;
|
||||
|
||||
/// 关闭弹框,动画结束后执行 completion(用于关闭后再 present 其他页面)
|
||||
- (void)dismissWithCompletion:(nullable void(^)(void))completion;
|
||||
|
||||
|
||||
-(void)resetUI;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,654 @@
|
||||
//
|
||||
// AStationDetailPopupController.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/22.
|
||||
//
|
||||
|
||||
#import "AStationDetailPopupController.h"
|
||||
#import "ABaseViewController.h"
|
||||
#import "AMapNavCommonUtil.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
// 主题绿色(开始导航按钮背景)
|
||||
static inline UIColor *AStationThemeGreen(void) {
|
||||
return [UIColor colorWithRed:0x1A/255.0 green:0x7C/255.0 blue:0x43/255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
@interface AStationDetailPopupController ()
|
||||
|
||||
/// 背景蒙层
|
||||
@property (nonatomic, strong) UIControl *maskControl;
|
||||
|
||||
/// 弹框卡片容器
|
||||
@property (nonatomic, strong) UIView *cardView;
|
||||
|
||||
/// 站点名称
|
||||
@property (nonatomic, strong) UILabel *stationNameLabel;
|
||||
|
||||
/// 预计加氢费用(名称右侧)
|
||||
@property (nonatomic, strong) UIImageView *costIconView;
|
||||
@property (nonatomic, strong) UILabel *costLabel;
|
||||
|
||||
/// 地址
|
||||
@property (nonatomic, strong) UILabel *addressLabel;
|
||||
|
||||
/// 分割线
|
||||
@property (nonatomic, strong) UIView *separator;
|
||||
|
||||
/// 预计时间行
|
||||
@property (nonatomic, strong) UIImageView *timeIconView;
|
||||
@property (nonatomic, strong) UILabel *timeLabel;
|
||||
|
||||
/// 行驶里程
|
||||
@property (nonatomic, strong) UIImageView *distanceIconView;
|
||||
@property (nonatomic, strong) UILabel *distanceLabel;
|
||||
|
||||
/// 过路费
|
||||
@property (nonatomic, strong) UIImageView *tollIconView;
|
||||
@property (nonatomic, strong) UILabel *tollLabel;
|
||||
|
||||
/// 关闭按钮
|
||||
@property (nonatomic, strong) UIButton *closeButton;
|
||||
|
||||
/// 开始导航按钮
|
||||
@property (nonatomic, strong) UIButton *startNaviButton;
|
||||
|
||||
/// 卡片 bottom constraint(动画用)
|
||||
@property (nonatomic, strong) MASConstraint *cardBottomConstraint;
|
||||
|
||||
// ── 营业时间 ──
|
||||
@property (nonatomic, strong) UILabel *businessHoursLabel;
|
||||
|
||||
// ── 站点联系人 ──
|
||||
@property (nonatomic, strong) UIImageView *contactPersonIconView;
|
||||
@property (nonatomic, strong) UILabel *contactPersonLabel;
|
||||
|
||||
// ── 加氢价格 ──
|
||||
@property (nonatomic, strong) UIImageView *priceIconView;
|
||||
@property (nonatomic, strong) UILabel *priceLabel;
|
||||
|
||||
// ── 联系方式 ──
|
||||
@property (nonatomic, strong) UIImageView *phoneIconView;
|
||||
@property (nonatomic, strong) UILabel *phoneLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AStationDetailPopupController
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
[self _buildUI];
|
||||
[self _setupMasonryConstraints];
|
||||
[self _updateUI];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self _playShowAnimation];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)presentInViewController:(UIViewController *)parentVC {
|
||||
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
[parentVC presentViewController:self animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)dismiss {
|
||||
[self dismissWithCompletion:nil];
|
||||
}
|
||||
|
||||
- (void)dismissWithCompletion:(void(^)(void))completion {
|
||||
[self _playDismissAnimationWithCompletion:^{
|
||||
[self dismissViewControllerAnimated:NO completion:completion];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Setter Override(弹出前赋值 或 弹出后动态更新)
|
||||
|
||||
- (void)setPointModel:(ANavPointModel *)pointModel {
|
||||
_pointModel = pointModel;
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setEstimatedCost:(NSString *)estimatedCost {
|
||||
_estimatedCost = [estimatedCost copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setEstimatedTime:(NSString *)estimatedTime {
|
||||
_estimatedTime = [estimatedTime copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setDriveDistance:(NSString *)driveDistance {
|
||||
_driveDistance = [driveDistance copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setTollFee:(NSString *)tollFee {
|
||||
_tollFee = [tollFee copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setBusinessHours:(NSString *)businessHours {
|
||||
_businessHours = [businessHours copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setContactName:(NSString *)contactName {
|
||||
_contactName = [contactName copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setContactPhone:(NSString *)contactPhone {
|
||||
_contactPhone = [contactPhone copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setHydrogenPrice:(NSString *)hydrogenPrice {
|
||||
_hydrogenPrice = [hydrogenPrice copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
#pragma mark - Build UI
|
||||
|
||||
/**
|
||||
卡片结构:
|
||||
┌──────────────────────────────────── [✕] ┐
|
||||
│ 站点名称(加粗) 预计加氢费用:--元 │ ← 同行,间距20pt
|
||||
│ 地址(灰色小字,多行) │
|
||||
│ ──────────────────────────────────────── │
|
||||
│ [icon] 预计时间:-- 分钟 │
|
||||
│ [icon] 行驶里程:-- 公里 [icon] 过路费:-- 元 │
|
||||
│ ╔════════════════════════════════════╗ │
|
||||
│ ║ 开始导航 ║ │
|
||||
│ ╚════════════════════════════════════╝ │
|
||||
│ (距底部30pt) │
|
||||
└─────────────────────────────────────────┘
|
||||
*/
|
||||
- (void)_buildUI {
|
||||
// ── 蒙层 ──
|
||||
UIControl *mask = [[UIControl alloc] init];
|
||||
mask.backgroundColor = [UIColor colorWithWhite:0 alpha:0.35];
|
||||
[mask addTarget:self action:@selector(_onMaskTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:mask];
|
||||
self.maskControl = mask;
|
||||
|
||||
// ── 卡片 ──
|
||||
UIView *card = [[UIView alloc] init];
|
||||
card.backgroundColor = [UIColor whiteColor];
|
||||
card.layer.cornerRadius = 16;
|
||||
card.layer.masksToBounds = NO;
|
||||
card.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
card.layer.shadowOpacity = 0.15;
|
||||
card.layer.shadowRadius = 12;
|
||||
card.layer.shadowOffset = CGSizeMake(0, -4);
|
||||
[self.view addSubview:card];
|
||||
self.cardView = card;
|
||||
|
||||
// ── 关闭按钮 ──
|
||||
UIButton *closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
// [closeBtn setTitle:@"✕" forState:UIControlStateNormal];
|
||||
[closeBtn setImage: [AMapNavCommonUtil imageWithName:@"icon_close"] forState:UIControlStateNormal];
|
||||
|
||||
[closeBtn setTitleColor:[UIColor colorWithWhite:0.5 alpha:1] forState:UIControlStateNormal];
|
||||
closeBtn.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[closeBtn addTarget:self action:@selector(_onCloseTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[card addSubview:closeBtn];
|
||||
self.closeButton = closeBtn;
|
||||
|
||||
UIColor * headTextColor = [UIColor hp_colorWithRGBHex:0x1D2129];
|
||||
UIFont * headTextFont = [UIFont hp_pingFangMedium:14];
|
||||
|
||||
// ── 站点名称 ──
|
||||
UILabel *nameLabel = [[UILabel alloc] init];
|
||||
nameLabel.font = [UIFont hp_pingFangMedium:18];
|
||||
nameLabel.textColor = headTextColor;
|
||||
nameLabel.numberOfLines = 2;
|
||||
// nameLabel.adjustsFontSizeToFitWidth = YES;
|
||||
nameLabel.minimumScaleFactor = 0.8;
|
||||
[card addSubview:nameLabel];
|
||||
self.stationNameLabel = nameLabel;
|
||||
|
||||
// ── 预计加氢费用(名称右侧,间距20pt) ──
|
||||
UILabel *costLabel = [[UILabel alloc] init];
|
||||
costLabel.font = headTextFont;
|
||||
costLabel.textColor = headTextColor;
|
||||
costLabel.numberOfLines = 1;
|
||||
costLabel.textAlignment = NSTextAlignmentLeft;
|
||||
// [costLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
// [costLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[card addSubview:costLabel];
|
||||
self.costLabel = costLabel;
|
||||
|
||||
// ── 营业时间(站点名称下方,4pt间距,浅灰色小字) ──
|
||||
UILabel *bizHoursLabel = [[UILabel alloc] init];
|
||||
bizHoursLabel.font = [UIFont hp_pingFangRegular:14];
|
||||
bizHoursLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
|
||||
bizHoursLabel.numberOfLines = 1;
|
||||
[card addSubview:bizHoursLabel];
|
||||
self.businessHoursLabel = bizHoursLabel;
|
||||
|
||||
// ── 地址 ──
|
||||
UILabel *addrLabel = [[UILabel alloc] init];
|
||||
addrLabel.font = [UIFont hp_pingFangRegular:14];
|
||||
addrLabel.textColor = [UIColor hp_colorWithRGBHex:0x86909C];
|
||||
addrLabel.numberOfLines = 2;
|
||||
[card addSubview:addrLabel];
|
||||
self.addressLabel = addrLabel;
|
||||
|
||||
UIImageView *costIcon = [[UIImageView alloc] init];
|
||||
costIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
costIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_fuel"];
|
||||
[card addSubview:costIcon];
|
||||
self.costIconView = costIcon;
|
||||
|
||||
// ── 分割线 ──
|
||||
UIView *sep = [[UIView alloc] init];
|
||||
sep.backgroundColor = [UIColor colorWithWhite:0.88 alpha:1];
|
||||
[card addSubview:sep];
|
||||
self.separator = sep;
|
||||
|
||||
// ── 预计时间图标 ──
|
||||
UIImageView *timeIcon = [[UIImageView alloc] init];
|
||||
timeIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
timeIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_time"];
|
||||
[card addSubview:timeIcon];
|
||||
self.timeIconView = timeIcon;
|
||||
|
||||
// ── 预计时间文字 ──
|
||||
UILabel *timeLabel = [[UILabel alloc] init];
|
||||
timeLabel.font = headTextFont;
|
||||
timeLabel.textColor = headTextColor;
|
||||
[card addSubview:timeLabel];
|
||||
self.timeLabel = timeLabel;
|
||||
|
||||
// ── 行驶里程图标 ──
|
||||
UIImageView *distIcon = [[UIImageView alloc] init];
|
||||
distIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
distIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_mileage"];
|
||||
[card addSubview:distIcon];
|
||||
self.distanceIconView = distIcon;
|
||||
|
||||
// ── 行驶里程文字 ──
|
||||
UILabel *distLabel = [[UILabel alloc] init];
|
||||
distLabel.font = headTextFont;
|
||||
distLabel.textColor = headTextColor;
|
||||
[card addSubview:distLabel];
|
||||
self.distanceLabel = distLabel;
|
||||
|
||||
// ── 过路费图标 ──
|
||||
UIImageView *tollIcon = [[UIImageView alloc] init];
|
||||
tollIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_toll"];
|
||||
[card addSubview:tollIcon];
|
||||
self.tollIconView = tollIcon;
|
||||
|
||||
// ── 过路费文字 ──
|
||||
UILabel *tollLabel = [[UILabel alloc] init];
|
||||
tollLabel.font = headTextFont;
|
||||
tollLabel.textColor = headTextColor;
|
||||
[card addSubview:tollLabel];
|
||||
self.tollLabel = tollLabel;
|
||||
|
||||
// ── 站点联系人图标 & 文字 ──
|
||||
UIImageView *personIcon = [[UIImageView alloc] init];
|
||||
personIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
personIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_person"];
|
||||
[card addSubview:personIcon];
|
||||
self.contactPersonIconView = personIcon;
|
||||
|
||||
UILabel *personLabel = [[UILabel alloc] init];
|
||||
personLabel.font = headTextFont;
|
||||
personLabel.textColor = headTextColor;
|
||||
[card addSubview:personLabel];
|
||||
self.contactPersonLabel = personLabel;
|
||||
|
||||
// ── 加氢价格图标 & 文字 ──
|
||||
UIImageView *priceIcon = [[UIImageView alloc] init];
|
||||
priceIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
priceIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_price"];
|
||||
[card addSubview:priceIcon];
|
||||
self.priceIconView = priceIcon;
|
||||
|
||||
UILabel *priceLabel = [[UILabel alloc] init];
|
||||
priceLabel.font = headTextFont;
|
||||
priceLabel.textColor = headTextColor;
|
||||
[card addSubview:priceLabel];
|
||||
self.priceLabel = priceLabel;
|
||||
|
||||
// ── 联系方式图标 & 文字 ──
|
||||
UIImageView *phoneIcon = [[UIImageView alloc] init];
|
||||
phoneIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
phoneIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_phone"];
|
||||
[card addSubview:phoneIcon];
|
||||
self.phoneIconView = phoneIcon;
|
||||
|
||||
UILabel *phoneLabel = [[UILabel alloc] init];
|
||||
phoneLabel.font = headTextFont;
|
||||
phoneLabel.textColor = headTextColor;
|
||||
[card addSubview:phoneLabel];
|
||||
self.phoneLabel = phoneLabel;
|
||||
|
||||
// ── 开始导航按钮 ──
|
||||
UIButton *naviBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[naviBtn setTitle:@"开始导航" forState:UIControlStateNormal];
|
||||
[naviBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
naviBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17];
|
||||
naviBtn.backgroundColor = AStationThemeGreen();
|
||||
naviBtn.layer.cornerRadius = 24;
|
||||
[naviBtn addTarget:self action:@selector(_onStartNaviTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[card addSubview:naviBtn];
|
||||
self.startNaviButton = naviBtn;
|
||||
}
|
||||
|
||||
#pragma mark - Masonry Constraints
|
||||
|
||||
- (void)_setupMasonryConstraints {
|
||||
UIView *card = self.cardView;
|
||||
CGFloat iconSize = 16;
|
||||
|
||||
// ── 蒙层:铺满父视图 ──
|
||||
[self.maskControl mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.view);
|
||||
}];
|
||||
|
||||
// ── 卡片:左右各16,底部距 safeArea 30pt ──
|
||||
// 初始状态卡片在屏幕下方(动画起点),top 留空,先用 bottom 约束做弹入
|
||||
[card mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(16);
|
||||
make.right.equalTo(self.view).offset(-16);
|
||||
// 动画起点:卡片顶部 = 父视图底部(完全隐藏在屏幕外)
|
||||
make.top.equalTo(self.view.mas_bottom).offset(0);
|
||||
}];
|
||||
|
||||
// ── 关闭按钮:右上角 ──
|
||||
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(card).offset(8);
|
||||
make.right.equalTo(card).offset(-15);
|
||||
make.width.height.mas_equalTo(40);
|
||||
}];
|
||||
|
||||
// ── 第一行:站点名称(左,内容自适应)+ 预计加氢费用(紧跟nameLabel右侧20pt) ──
|
||||
// nameLabel:内容有多宽占多宽,宽度上限55%,防止过长把费用挤掉
|
||||
[self.stationNameLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
|
||||
forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[self.stationNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(card).offset(25);
|
||||
make.left.equalTo(card).offset(16);
|
||||
// make.width.lessThanOrEqualTo(card).multipliedBy(0.45);
|
||||
make.right.equalTo(self.closeButton.mas_left).offset(-12);
|
||||
}];
|
||||
|
||||
// ── 营业时间(站点名称下方,6pt间距) ──
|
||||
[self.businessHoursLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.stationNameLabel.mas_bottom).offset(10);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
}];
|
||||
|
||||
// ── 地址(营业时间下方,6pt间距) ──
|
||||
[self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.businessHoursLabel.mas_bottom).offset(10);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
}];
|
||||
self.separator.hidden = YES;
|
||||
|
||||
// ── 分割线(地址下方,12pt间距) ──
|
||||
[self.separator mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.addressLabel.mas_bottom).offset(10);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
CGFloat _offset_y = 18;
|
||||
|
||||
// ── 预计时间行(分割线下方,14pt) ──
|
||||
[self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separator.mas_bottom).offset(12);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.timeIconView);
|
||||
make.left.equalTo(self.timeIconView.mas_right).offset(6);
|
||||
// make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
///cost
|
||||
[self.costIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.timeIconView);
|
||||
make.left.equalTo(self.timeLabel.mas_right).offset(40);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.costLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.costIconView);
|
||||
make.left.equalTo(self.costIconView.mas_right).offset(6);
|
||||
make.right.lessThanOrEqualTo(card).offset(-10);
|
||||
}];
|
||||
|
||||
|
||||
// ── 行驶里程 + 过路费行(时间行下方,12pt) ──
|
||||
[self.distanceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.timeIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.distanceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.distanceIconView);
|
||||
make.left.equalTo(self.distanceIconView.mas_right).offset(6);
|
||||
// 宽度约为可用区域的一半(另一半留给过路费)
|
||||
make.width.mas_lessThanOrEqualTo(130);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
[self.tollIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.distanceIconView);
|
||||
make.left.equalTo(self.costIconView.mas_left);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.tollLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.distanceIconView);
|
||||
make.left.equalTo(self.tollIconView.mas_right).offset(6);
|
||||
// make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 站点联系人行(过路费行下方,14pt) ──
|
||||
[self.contactPersonIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.distanceIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.contactPersonLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contactPersonIconView);
|
||||
make.left.equalTo(self.contactPersonIconView.mas_right).offset(6);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 加氢价格(与站点联系人同行,右侧对齐 costIconView) ──
|
||||
[self.priceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contactPersonIconView);
|
||||
make.left.equalTo(self.costIconView.mas_left);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contactPersonIconView);
|
||||
make.left.equalTo(self.priceIconView.mas_right).offset(6);
|
||||
make.right.lessThanOrEqualTo(card).offset(-10);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 联系方式行(站点联系人行下方,12pt) ──
|
||||
[self.phoneIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contactPersonIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.phoneLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.phoneIconView);
|
||||
make.left.equalTo(self.phoneIconView.mas_right).offset(6);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 开始导航按钮(联系方式行下方18pt,距卡片底部 safeArea) ──
|
||||
[self.startNaviButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.phoneIconView.mas_bottom).offset(40);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(48);
|
||||
make.bottom.equalTo(card).offset(-AMP_TabbarSafeBottomMargin); // 卡片底部内间距30pt
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Data Update
|
||||
|
||||
- (void)_updateUI {
|
||||
self.stationNameLabel.text = (self.pointModel.name.length > 0)
|
||||
? self.pointModel.name : @"-";
|
||||
|
||||
// ── 营业时间 ──
|
||||
self.businessHoursLabel.text = (self.businessHours.length > 0)
|
||||
? [NSString stringWithFormat:@"营业时间:%@", self.businessHours]
|
||||
: @"营业时间:-";
|
||||
|
||||
self.costLabel.text = (self.estimatedCost.length > 0)
|
||||
? [NSString stringWithFormat:@"预计加氢费用:%@元", self.estimatedCost]
|
||||
: @"预计加氢费用:-";
|
||||
|
||||
self.addressLabel.text = (self.pointModel.address.length > 0)
|
||||
? self.pointModel.address : @"-";
|
||||
|
||||
// ── 预计时间(始终显示,无值显示"-- 分钟") ──
|
||||
self.timeLabel.text = (self.estimatedTime.length > 0)
|
||||
? [NSString stringWithFormat:@"预计时间:%@分钟", self.estimatedTime]
|
||||
: @"预计时间:-";
|
||||
|
||||
// ── 行驶里程(始终显示,无值显示"-- 公里") ──
|
||||
self.distanceLabel.text = (self.driveDistance.length > 0)
|
||||
? [NSString stringWithFormat:@"行驶里程:%@公里", self.driveDistance]
|
||||
: @"行驶里程:-";
|
||||
|
||||
// ── 过路费(始终显示,无值显示"-- 元") ──
|
||||
self.tollLabel.text = (self.tollFee.length > 0)
|
||||
? [NSString stringWithFormat:@"过路费:%@元", self.tollFee]
|
||||
: @"过路费:-";
|
||||
|
||||
// ── 站点联系人(有值显示值,无值显示 -) ──
|
||||
self.contactPersonLabel.text = (self.contactName.length > 0)
|
||||
? [NSString stringWithFormat:@"站联系人:%@", self.contactName]
|
||||
: @"站联系人:-";
|
||||
|
||||
// ── 加氢价格(有值显示值,无值显示 -) ──
|
||||
self.priceLabel.text = (self.hydrogenPrice.length > 0)
|
||||
? [NSString stringWithFormat:@"加氢价格:%@/L", self.hydrogenPrice]
|
||||
: @"加氢价格:-";
|
||||
|
||||
// ── 联系方式(有值显示值,无值显示 -) ──
|
||||
self.phoneLabel.text = (self.contactPhone.length > 0)
|
||||
? [NSString stringWithFormat:@"联系方式:%@", self.contactPhone]
|
||||
: @"联系方式:-";
|
||||
}
|
||||
|
||||
// 重置 UI 状态
|
||||
-(void)resetUI {
|
||||
self.stationNameLabel.text = @"-";
|
||||
self.businessHoursLabel.text = @"营业时间:-";
|
||||
self.costLabel.text = @"预计加氢费用:-";
|
||||
self.addressLabel.text = @"地址:-";
|
||||
self.timeLabel.text = @"预计时间:-";
|
||||
self.distanceLabel.text = @"行驶里程:-";
|
||||
self.tollLabel.text = @"过路费:-";
|
||||
self.contactPersonLabel.text = @"站联系人:-";
|
||||
self.priceLabel.text = @"加氢价格:-";
|
||||
self.phoneLabel.text = @"联系方式:-";
|
||||
}
|
||||
|
||||
#pragma mark - Animation
|
||||
|
||||
/**
|
||||
弹入动画:更新 top 约束将卡片滑入至距底部 30pt(含 safeArea)
|
||||
*/
|
||||
- (void)_playShowAnimation {
|
||||
// 目标状态:卡片底部紧贴 safeArea 底部,再往上留 30pt
|
||||
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(0);
|
||||
make.right.equalTo(self.view).offset(-0);
|
||||
make.bottom.equalTo(self.view).offset(0);
|
||||
}];
|
||||
|
||||
self.maskControl.alpha = 0;
|
||||
[UIView animateWithDuration:0.36
|
||||
delay:0
|
||||
usingSpringWithDamping:0.82
|
||||
initialSpringVelocity:0.5
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
self.maskControl.alpha = 1;
|
||||
[self.view layoutIfNeeded];
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)_playDismissAnimationWithCompletion:(void(^)(void))completion {
|
||||
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(16);
|
||||
make.right.equalTo(self.view).offset(-16);
|
||||
make.top.equalTo(self.view.mas_bottom).offset(20);
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:0.25
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveEaseIn
|
||||
animations:^{
|
||||
self.maskControl.alpha = 0;
|
||||
[self.view layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
if (completion) completion();
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)_onMaskTapped {
|
||||
if ([self.delegate respondsToSelector:@selector(stationDetailPopupDidTapClose:)]) {
|
||||
[self.delegate stationDetailPopupDidTapClose:self];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)_onCloseTapped {
|
||||
if ([self.delegate respondsToSelector:@selector(stationDetailPopupDidTapClose:)]) {
|
||||
[self.delegate stationDetailPopupDidTapClose:self];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)_onStartNaviTapped {
|
||||
// 先关闭弹框(等动画完全结束),再通知 delegate 弹出导航页
|
||||
// 避免两个 presentViewController 同时进行导致导航页面无法显示
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self dismissWithCompletion:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if ([strongSelf.delegate respondsToSelector:@selector(stationDetailPopupDidTapStartNavi:)]) {
|
||||
[strongSelf.delegate stationDetailPopupDidTapStartNavi:strongSelf];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// AAlgorithmPathModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AAlgorithmPathModel : NSObject
|
||||
|
||||
/// 单程
|
||||
@property (nonatomic, copy, nullable) NSString *tripRecommendationId;
|
||||
@property (nonatomic, copy, nullable) NSString *staId;
|
||||
@property (nonatomic, copy, nullable) NSString *tripOneWayPathId;
|
||||
@property (nonatomic, copy, nullable) NSString *tripReturnPathId;
|
||||
|
||||
/// 里程(公里)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayDis;
|
||||
@property (nonatomic, copy, nullable) NSString *returnDis;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripDis;
|
||||
|
||||
/// 时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayTime;
|
||||
@property (nonatomic, copy, nullable) NSString *returnTime;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripTime;
|
||||
|
||||
/// 总成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripCost;
|
||||
|
||||
/// 人工成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayLaborCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnLaborCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripLaborCost;
|
||||
|
||||
/// 高速成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayChargerouteCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnChargerouteCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripChargerouteCost;
|
||||
|
||||
/// 氢耗(公斤)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayHydrogenConsumption;
|
||||
@property (nonatomic, copy, nullable) NSString *returnLaborHydrogenConsumption;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripHydrogenConsumption;
|
||||
|
||||
/// 氢气成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayHydrogenCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnLaborHydrogenCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripHydrogenCost;
|
||||
|
||||
/// 加氢站相关
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenCost; // 氢气总成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaServiceTime; // 站服务总时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaRefuelingTime;// 实际加油时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaQueueTime; // 排队时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaServiceTimeCost; // 站服务时间成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaRefuelingTimeCost;// 加油时间成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaQueueTimeCost; // 排队时间成本(元)
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// AAlgorithmPathModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "AAlgorithmPathModel.h"
|
||||
|
||||
@implementation AAlgorithmPathModel
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// ACustomPointAnnotation.h
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <AMapNaviKit/AMapNaviKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ACustomPointAnnotation : MAPointAnnotation
|
||||
@property (nonatomic, copy, nullable) NSString *stationID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// ACustomPointAnnotation.m
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "ACustomPointAnnotation.h"
|
||||
|
||||
@implementation ACustomPointAnnotation
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// AMapHyStationModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/2/11.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
{
|
||||
"name": "嘉兴经开站",
|
||||
"shortName": null,
|
||||
"siteNo": null,
|
||||
"city": null,
|
||||
"address": "嘉兴市秀洲区岗山路272号",
|
||||
"contact": "龚明伟",
|
||||
"phone": "18888888888",
|
||||
"type": null,
|
||||
"coOpMode": null,
|
||||
"booking": null,
|
||||
"siteStatus": 0,
|
||||
"startBusiness": "06:00:00",
|
||||
"endBusiness": "22:00:00",
|
||||
"billingMethod": null,
|
||||
"term": null,
|
||||
"remark": null,
|
||||
"longitude": "120.75972800",
|
||||
"latitude": "30.79962800"
|
||||
}
|
||||
*/
|
||||
@interface AMapHyStationModel : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *ID;
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenId;
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@property (nonatomic, copy, nullable) NSString *shortName;
|
||||
@property (nonatomic, copy, nullable) NSString *siteNo;
|
||||
@property (nonatomic, copy, nullable) NSString *city;
|
||||
@property (nonatomic, copy, nullable) NSString *address;
|
||||
@property (nonatomic, copy, nullable) NSString *contact;
|
||||
@property (nonatomic, copy, nullable) NSString *phone;
|
||||
@property (nonatomic, copy, nullable) NSString *type;
|
||||
@property (nonatomic, copy, nullable) NSString *coOpMode;
|
||||
@property (nonatomic, strong, nullable) NSString * booking;
|
||||
@property (nonatomic, assign) NSInteger siteStatus;
|
||||
@property (nonatomic, copy, nullable) NSString *startBusiness;
|
||||
@property (nonatomic, copy, nullable) NSString *endBusiness;
|
||||
@property (nonatomic, copy, nullable) NSString *billingMethod;
|
||||
@property (nonatomic, copy, nullable) NSString *term;
|
||||
@property (nonatomic, copy, nullable) NSString *remark;
|
||||
@property (nonatomic, copy, nullable) NSString *longitude;
|
||||
@property (nonatomic, copy, nullable) NSString *latitude;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
{
|
||||
"code": 0,
|
||||
"status": true,
|
||||
"message": "success",
|
||||
"data": [],
|
||||
"time": "1770800256408",
|
||||
"error": null
|
||||
}
|
||||
*/
|
||||
|
||||
@interface AMapHyResponse : NSObject
|
||||
@property (nonatomic, assign) NSInteger code;
|
||||
@property (nonatomic, assign) NSInteger status;
|
||||
@property (nonatomic, copy, nullable) NSString *message;
|
||||
@property (nonatomic, copy, nullable) NSString *time;
|
||||
@property (nonatomic, copy, nullable) NSString *error;
|
||||
|
||||
@property(nonatomic , strong)NSArray <AMapHyStationModel * > * data;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// AMapHyStationModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/2/11.
|
||||
//
|
||||
|
||||
#import "AMapHyStationModel.h"
|
||||
|
||||
@implementation AMapHyStationModel
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName
|
||||
{
|
||||
return @{ @"ID" : @"id"};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation AMapHyResponse
|
||||
|
||||
+ (NSDictionary *)mj_objectClassInArray {
|
||||
return @{@"data" : AMapHyStationModel.class};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// ANavPointModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ANavPointModel : NSObject
|
||||
@property (nonatomic, copy, nullable) NSString *name;
|
||||
@property (nonatomic, copy, nullable) NSString *address;
|
||||
@property (nonatomic, copy, nullable) NSString *stationID;
|
||||
|
||||
///经纬度
|
||||
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
|
||||
|
||||
///初始化
|
||||
+ (instancetype)instanceWithCoordinate:(CLLocationCoordinate2D)coordinate name:(NSString *)name address:(NSString *)address;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ANavPointModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "ANavPointModel.h"
|
||||
|
||||
@implementation ANavPointModel
|
||||
///初始化
|
||||
+ (instancetype)instanceWithCoordinate:(CLLocationCoordinate2D)coordinate name:(NSString *)name address:(NSString *)address {
|
||||
ANavPointModel *instance = [[ANavPointModel alloc] init];
|
||||
if (instance) {
|
||||
instance.coordinate = coordinate;
|
||||
instance.name = name;
|
||||
instance.address = address;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// ANaviPathInfoModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
{
|
||||
"name": "浙江省嘉兴市平湖市乍浦镇滨海大道中国石化滨海大道加油加气站",
|
||||
"poiId": "",
|
||||
"coordinate": {
|
||||
"longitude": "121.070434",
|
||||
"latitude": "30.596124"
|
||||
}
|
||||
}
|
||||
*/
|
||||
@interface ANaviPathInfoModel : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *name;
|
||||
@property (nonatomic, copy, nullable) NSString *poiId;
|
||||
@property (nonatomic, copy, nullable) NSString *longitude;
|
||||
@property (nonatomic, copy, nullable) NSString *latitude;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// ANaviPathInfoModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "ANaviPathInfoModel.h"
|
||||
|
||||
@implementation ANaviPathInfoModel
|
||||
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
// JSON 中 coordinate 是嵌套对象,展开为 longitude/latitude
|
||||
return @{
|
||||
@"longitude": @"coordinate.longitude",
|
||||
@"latitude": @"coordinate.latitude"
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||