Android 基于高德SDK开发的导航项目(入门级)

打印 上一主题 下一主题

主题 1852|帖子 1852|积分 5556

Android 基于高德 SDK开发的导航项目(入门级)

项目实现了一个功能完备的导航体系,包含:


  • 地图显示和控制
  • 门路规划和导航
  • 语音播报功能
  • 实时路况监控
  • 安全提醒体系
  • 气候信息和预警
  • 智能出行建议
一、高德地图KEY的申请

①、进入高德的控制台:https://tengyun-console.amap.com
②、完成注册后,点击右边的 应用管理→我的应用→创建新应用→获取KEY (不会的就跟着我下面的步骤来,包会的)





如何获取自己的测试版安全码SHA1:

上述的命令:keytool -list -v -keystore debug.keystore
二、配置安卓干系权限和接入高德KEY

通过上述的操纵我们已经获取了高德地图的KEY,接下来就是对KEY的接入和对项目的权限配置
1、应用高德KEY

在AndroidManifest.xml中标签里面但不要在activity 标签下粘贴:
  1. <meta-data
  2.             android:name="com.amap.api.v2.apikey"
  3.             android:value="换成自己的高德KEY" />
复制代码
2、配置权限

这些是大概会用到的权限,请在AndroidManifest.xml中粘贴(不要在标签里面粘贴,会报错):
  1. <!-- 允许程序访问互联网 -->
  2. <uses-permission android:name="android.permission.INTERNET" />
  3. <!-- 允许程序设置内置sd卡的写权限 -->
  4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  5. <!-- 允许程序获取网络状态 -->
  6. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  7. <!-- 允许程序访问WiFi网络信息 -->
  8. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  9. <!-- 允许程序读写手机状态和身份 -->
  10. <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  11. <!-- 允许程序访问CellID或WiFi热点来获取粗略的位置 -->
  12. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  13. <!-- 允许程序通过GPS芯片接收卫星的定位信息 -->
  14. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  15. <!-- 允许程序修改全局音频设置 -->
  16. <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
  17. <!-- 允许程序录制音频,用于语音识别功能 -->
  18. <uses-permission android:name="android.permission.RECORD_AUDIO" />  
  19. <!-- 允许程序控制手机振动器 -->
  20. <uses-permission android:name="android.permission.VIBRATE" />
复制代码
三、Android 地图SDK 干系下载与引入

①登录高德地图站点:https://lbs.amap.com/api/android-sdk/download


②将下载的压缩包进行压缩(最好存放在一起我们等会要用)


③引入 JAR包到Android



创建一个libs:

选中 JAR包并复制粘贴到libs下,右击这个包点击菜单栏里的“Add AS Library…”



这一步实在就是修改App应用下的“build.gradle”文件,添加了一个依赖 JAR包
在build.gradle(app)里应该会有这一行代码:implementation(files(“libs\AMap3DMap_10.1.200_AMapNavi_10.1.200_AMapSearch_9.7.4_AMapLocation_6.4.9_20241226.jar”))
④在Android应用的app/src/main目录下新建一个jniLibs子目录





末了就是这样的,到这里就算完成了配置,我们要正式开始写程序了
四、开发地图导航功能

1、地图的初始化创建(导入地图)

MainActivity里的代码

  1. import android.Manifest;
  2. import android.content.pm.PackageManager;
  3. import android.graphics.Color;
  4. import android.os.Bundle;
  5. import android.widget.Toast;
  6. import androidx.annotation.NonNull;
  7. import androidx.appcompat.app.AppCompatActivity;
  8. import androidx.core.app.ActivityCompat;
  9. import androidx.core.content.ContextCompat;
  10. import com.amap.api.maps.AMap;
  11. import com.amap.api.maps.MapView;
  12. import com.amap.api.maps.MapsInitializer;
  13. import com.amap.api.maps.model.MyLocationStyle;
  14. import com.amap.api.maps.CameraUpdateFactory;
  15. /**
  16. * 主活动类
  17. * 用于展示高德地图的主界面
  18. */
  19. public class MainActivity extends AppCompatActivity {
  20.     // 地图视图组件
  21.     private MapView mapView;
  22.     // 地图控制器对象
  23.     private AMap aMap;
  24.     // 定位权限请求码
  25.     private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
  26.     // 需要进行检测的权限数组
  27.     private static final String[] NEEDED_PERMISSIONS = {
  28.             Manifest.permission.ACCESS_FINE_LOCATION,
  29.             Manifest.permission.ACCESS_COARSE_LOCATION
  30.     };
  31.     @Override
  32.     protected void onCreate(Bundle savedInstanceState) {
  33.         super.onCreate(savedInstanceState);
  34.         
  35.         // 设置高德地图隐私政策
  36.         // 更新同意隐私状态,需要在初始化地图之前完成
  37.         //如果没有这两行代码,你的地图可能会不显示
  38.         MapsInitializer.updatePrivacyShow(this, true, true);  // 展示隐私合规对话框
  39.         MapsInitializer.updatePrivacyAgree(this, true);      // 同意隐私合规政策
  40.         
  41.         setContentView(R.layout.activity_main);
  42.         
  43.         // 初始化地图组件
  44.         mapView = findViewById(R.id.map);
  45.         // 必须回调MapView的onCreate方法,否则地图无法显示
  46.         mapView.onCreate(savedInstanceState);
  47.         
  48.         // 初始化地图控制器
  49.         // 只有在aMap为空时才重新获取,避免重复初始化
  50.         if (aMap == null) {
  51.             aMap = mapView.getMap();
  52.             // 设置地图UI控件
  53.             setupMapUI();
  54.         }
  55.         // 检查并请求定位权限
  56.         checkAndRequestPermissions();
  57.     }
  58.     /**
  59.      * 设置地图UI和定位样式
  60.      */
  61.     private void setupMapUI() {
  62.         // 设置定位蓝点的样式
  63.         MyLocationStyle myLocationStyle = new MyLocationStyle();
  64.         // 连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动
  65.         myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);
  66.         // 设置定位蓝点的Style
  67.         myLocationStyle.strokeColor(Color.argb(180, 3, 145, 255));// 设置圆形的边框颜色
  68.         myLocationStyle.radiusFillColor(Color.argb(10, 0, 0, 180));// 设置圆形的填充颜色
  69.         myLocationStyle.strokeWidth(5.0f);// 设置圆形的边框粗细
  70.         // 设置定位蓝点的 Style
  71.         aMap.setMyLocationStyle(myLocationStyle);
  72.         // 设置定位监听
  73.         aMap.setOnMyLocationChangeListener(location -> {
  74.             // 当位置发生变化时,将地图中心点移动到当前位置
  75.             aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
  76.                     new com.amap.api.maps.model.LatLng(location.getLatitude(), location.getLongitude()),
  77.                     17)); // 设置缩放级别为17
  78.         });
  79.     }
  80.     /**
  81.      * 检查并请求定位权限
  82.      */
  83.     private void checkAndRequestPermissions() {
  84.         if (lacksPermissions(NEEDED_PERMISSIONS)) {
  85.             ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, LOCATION_PERMISSION_REQUEST_CODE);
  86.         } else {
  87.             // 已经有权限,启用定位图层
  88.             enableMyLocation();
  89.         }
  90.     }
  91.     /**
  92.      * 检查是否缺少权限
  93.      */
  94.     private boolean lacksPermissions(String... permissions) {
  95.         for (String permission : permissions) {
  96.             if (ContextCompat.checkSelfPermission(this, permission) !=
  97.                     PackageManager.PERMISSION_GRANTED) {
  98.                 return true;
  99.             }
  100.         }
  101.         return false;
  102.     }
  103.     /**
  104.      * 处理权限请求结果
  105.      */
  106.     @Override
  107.     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
  108.             @NonNull int[] grantResults) {
  109.         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  110.         if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
  111.             if (grantResults.length > 0) {
  112.                 boolean allGranted = true;
  113.                 for (int grantResult : grantResults) {
  114.                     if (grantResult != PackageManager.PERMISSION_GRANTED) {
  115.                         allGranted = false;
  116.                         break;
  117.                     }
  118.                 }
  119.                 if (allGranted) {
  120.                     // 获得权限,启用定位图层
  121.                     enableMyLocation();
  122.                 } else {
  123.                     Toast.makeText(this, "需要定位权限才能显示当前位置", Toast.LENGTH_SHORT).show();
  124.                 }
  125.             }
  126.         }
  127.     }
  128.     /**
  129.      * 启用定位图层
  130.      */
  131.     private void enableMyLocation() {
  132.         // 设置为true表示启动显示定位蓝点
  133.         aMap.setMyLocationEnabled(true);
  134.     }
  135.     /**
  136.      * Activity生命周期方法 - 恢复
  137.      * 必须回调MapView的onResume方法,否则地图会处于暂停状态
  138.      */
  139.     @Override
  140.     protected void onResume() {
  141.         super.onResume();
  142.         mapView.onResume();
  143.     }
  144.     /**
  145.      * Activity生命周期方法 - 暂停
  146.      * 必须回调MapView的onPause方法,暂停地图的绘制
  147.      */
  148.     @Override
  149.     protected void onPause() {
  150.         super.onPause();
  151.         mapView.onPause();
  152.     }
  153.     /**
  154.      * Activity生命周期方法 - 保存状态
  155.      * 保存地图当前的状态,在重建Activity时恢复状态
  156.      */
  157.     @Override
  158.     protected void onSaveInstanceState(Bundle outState) {
  159.         super.onSaveInstanceState(outState);
  160.         mapView.onSaveInstanceState(outState);
  161.     }
  162.     /**
  163.      * Activity生命周期方法 - 销毁
  164.      * 必须回调MapView的onDestroy方法,销毁地图,释放资源
  165.      */
  166.     @Override
  167.     protected void onDestroy() {
  168.         super.onDestroy();
  169.         mapView.onDestroy();
  170.     }
  171. }
复制代码
activity_main.xml

  1. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:app="http://schemas.android.com/apk/res-auto"
  3.     xmlns:tools="http://schemas.android.com/tools"
  4.     android:id="@+id/main"
  5.     android:layout_width="match_parent"
  6.     android:layout_height="match_parent"
  7.     tools:context=".MainActivity">
  8.     <com.amap.api.maps.MapView
  9.         android:id="@+id/map"
  10.         android:layout_width="match_parent"
  11.         android:layout_height="match_parent"
  12.         app:layout_constraintBottom_toBottomOf="parent"
  13.         app:layout_constraintEnd_toEndOf="parent"
  14.         app:layout_constraintStart_toStartOf="parent"
  15.         app:layout_constraintTop_toTopOf="parent" />
  16. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码
通过上面的代码,我们能实现地图的导入,也乐成的实现了这个项目的第一步(用真机进行测试,虚拟机会报错

2、实现门路规划和导航

①类定义和接口实现

  1. public class MainActivity extends AppCompatActivity implements
  2.     RouteSearch.OnRouteSearchListener, // 路线搜索监听
  3.     AMapNaviListener // 导航监听
复制代码
写完以后会有这样的报错:那是因为接口里的方法没有写全

解决步骤:
将鼠标放在这行代码上,点击Alt+Enter,Android会自动告诉你解决方法


这样Android就会帮我补全缺失的方法了
②创建一个可以拉动的半透明的面板

在res/layout下创建一个layout Resource File文件 名字叫 bottom_sheet_layout
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:app="http://schemas.android.com/apk/res-auto"
  3.     android:id="@+id/bottom_sheet"
  4.     android:layout_width="match_parent"
  5.     android:layout_height="wrap_content"
  6.     android:background="#F5FFFFFF"
  7.     android:orientation="vertical"
  8.     android:padding="16dp"
  9.     app:behavior_hideable="false"
  10.     app:behavior_peekHeight="180dp"
  11.     app:behavior_draggable="true"
  12.     app:behavior_fitToContents="true"
  13.     app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
  14.     <!-- 拖动条指示器 -->
  15.     <View
  16.         android:layout_width="40dp"
  17.         android:layout_height="4dp"
  18.         android:layout_gravity="center_horizontal"
  19.         android:layout_marginBottom="16dp"
  20.         android:background="#CCCCCC" />
  21. </LinearLayout>
复制代码
③创建一个搜索框

在res/drawable下创建一个search_background
  1. <shape xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:shape="rectangle">
  3.     <solid android:color="#F5F5F5" />
  4.     <corners android:radius="24dp" />
  5.     <stroke
  6.         android:width="1dp"
  7.         android:color="#E0E0E0" />
  8. </shape>
复制代码
在Activity_main.xml文件
  1. <!-- 搜索框区域 -->
  2.         <LinearLayout
  3.             android:layout_width="match_parent"
  4.             android:layout_height="wrap_content"
  5.             android:orientation="horizontal"
  6.             android:padding="8dp"
  7.             android:layout_marginHorizontal="16dp"
  8.             android:layout_marginBottom="8dp"
  9.             android:background="@drawable/search_background"
  10.             android:elevation="2dp">
  11.             <!-- 搜索图标 -->
  12.             <ImageView
  13.                 android:id="@+id/search_icon"
  14.                 android:layout_width="24dp"
  15.                 android:layout_height="24dp"
  16.                 android:layout_gravity="center_vertical"
  17.                 android:layout_marginStart="8dp"
  18.                 android:src="@android:drawable/ic_search_category_default"
  19.                 app:tint="#666666" />
  20.             <!-- 搜索输入框 -->
  21.             <EditText
  22.                 android:id="@+id/search_input"
  23.                 android:layout_width="0dp"
  24.                 android:layout_height="40dp"
  25.                 android:layout_weight="1"
  26.                 android:background="@null"
  27.                 android:hint="搜索目的地"
  28.                 android:textColorHint="#999999"
  29.                 android:textColor="#333333"
  30.                 android:textSize="16sp"
  31.                 android:singleLine="true"
  32.                 android:imeOptions="actionSearch"
  33.                 android:layout_marginStart="8dp"
  34.                 android:layout_marginEnd="8dp" />
  35.         </LinearLayout>
复制代码
④规划和导航功能完备的代码



  • 导航初始化 :
    在 MainActivity.java 中,通过 initNaviAndRoute 方法初始化了导航功能,使用了高德地图的AMapNavi SDK。
  • 门路规划 :
    通过 planRoute 方法进行门路规划,支持驾车导航。
  • 导航控制 :
    在底部面板中有一个"开始导航"按钮,点击后会调用 startNavi 方法启动真实导航。
  • 导航事件监听 :
    实现了 AMapNaviListener 接口,处理惩罚各种导航事件,如:
  • 导航初始化乐成/失败
  • 开始导航
  • 到达目的地
  • 门路盘算乐成/失败
  • 位置变革等
  • 门路绘制 :
    通过 drawRouteLine 方法在地图上绘制导航门路。
  • 导航信息展示 :
    在底部面板中显示导航干系信息,包括目的地、距离和预计时间,通过 updateBottomSheetInfo 方法更新。
  1. import android.Manifest;
  2. import android.content.Context;
  3. import android.content.pm.PackageManager;
  4. import android.graphics.Color;
  5. import android.os.Bundle;
  6. import android.text.TextUtils;
  7. import android.view.View;
  8. import android.view.inputmethod.EditorInfo;
  9. import android.view.inputmethod.InputMethodManager;
  10. import android.widget.Button;
  11. import android.widget.EditText;
  12. import android.widget.TextView;
  13. import android.widget.Toast;
  14. import androidx.annotation.NonNull;
  15. import androidx.appcompat.app.AppCompatActivity;
  16. import androidx.core.app.ActivityCompat;
  17. import androidx.core.content.ContextCompat;
  18. import com.amap.api.maps.AMap;
  19. import com.amap.api.maps.MapView;
  20. import com.amap.api.maps.MapsInitializer;
  21. import com.amap.api.maps.model.MyLocationStyle;
  22. import com.amap.api.maps.CameraUpdateFactory;
  23. import com.amap.api.maps.model.LatLng;
  24. import com.amap.api.maps.model.Marker;
  25. import com.amap.api.maps.model.MarkerOptions;
  26. import com.amap.api.navi.AMapNavi;
  27. import com.amap.api.navi.AMapNaviListener;
  28. import com.amap.api.navi.enums.PathPlanningStrategy;
  29. import com.amap.api.navi.model.AMapCalcRouteResult;
  30. import com.amap.api.navi.model.AMapLaneInfo;
  31. import com.amap.api.navi.model.AMapModelCross;
  32. import com.amap.api.navi.model.AMapNaviCameraInfo;
  33. import com.amap.api.navi.model.AMapNaviCross;
  34. import com.amap.api.navi.model.AMapNaviLocation;
  35. import com.amap.api.navi.model.AMapNaviPath;
  36. import com.amap.api.navi.model.AMapNaviRouteNotifyData;
  37. import com.amap.api.navi.model.AMapNaviTrafficFacilityInfo;
  38. import com.amap.api.navi.model.AMapServiceAreaInfo;
  39. import com.amap.api.navi.model.AimLessModeCongestionInfo;
  40. import com.amap.api.navi.model.AimLessModeStat;
  41. import com.amap.api.navi.model.NaviInfo;
  42. import com.amap.api.navi.model.NaviLatLng;
  43. import com.amap.api.services.core.AMapException;
  44. import com.amap.api.services.core.LatLonPoint;
  45. import com.amap.api.services.core.PoiItem;
  46. import com.amap.api.services.route.BusRouteResult;
  47. import com.amap.api.services.route.DriveRouteResult;
  48. import com.amap.api.services.route.RideRouteResult;
  49. import com.amap.api.services.route.RouteSearch;
  50. import com.amap.api.services.route.WalkRouteResult;
  51. import com.google.android.material.bottomsheet.BottomSheetBehavior;
  52. import com.amap.api.services.poisearch.PoiSearch;
  53. import com.amap.api.services.poisearch.PoiResult;
  54. import com.amap.api.maps.model.PolylineOptions;
  55. import com.amap.api.maps.model.LatLngBounds;
  56. import java.util.ArrayList;
  57. import java.util.List;
  58. public class MainActivity extends AppCompatActivity implements RouteSearch.OnRouteSearchListener, AMapNaviListener, PoiSearch.OnPoiSearchListener {
  59.     // 地图视图组件
  60.     private MapView mapView;
  61.     // 地图控制器对象
  62.     private AMap aMap;
  63.     // 导航控制器对象
  64.     private AMapNavi mAMapNavi;
  65.     // 路线搜索对象
  66.     private RouteSearch routeSearch;
  67.     // 目的地标记
  68.     private Marker destinationMarker;
  69.     // 定位权限请求码
  70.     private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
  71.     // 需要进行检测的权限数组
  72.     private static final String[] NEEDED_PERMISSIONS = {
  73.             Manifest.permission.ACCESS_FINE_LOCATION,
  74.             Manifest.permission.ACCESS_COARSE_LOCATION
  75.     };
  76.     private BottomSheetBehavior<View> bottomSheetBehavior;
  77.     private TextView tvDestination, tvDistance, tvTime;
  78.     private Button btnStartNavi;
  79.     private LatLonPoint destinationPoint;  // 保存目的地坐标
  80.     @Override
  81.     protected void onCreate(Bundle savedInstanceState) {
  82.         super.onCreate(savedInstanceState);
  83.         
  84.         try {
  85.             MapsInitializer.updatePrivacyShow(this, true, true);
  86.             MapsInitializer.updatePrivacyAgree(this, true);
  87.             
  88.         setContentView(R.layout.activity_main);
  89.             
  90.             // 初始化底部面板
  91.             initBottomSheet();
  92.             
  93.             mapView = findViewById(R.id.map);
  94.             mapView.onCreate(savedInstanceState);
  95.             
  96.             if (aMap == null) {
  97.                 aMap = mapView.getMap();
  98.                 setupMapUI();
  99.             }
  100.             initNaviAndRoute();
  101.             checkAndRequestPermissions();
  102.         } catch (Exception e) {
  103.             e.printStackTrace();
  104.             Toast.makeText(this, "初始化失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  105.         }
  106.     }
  107.     /**
  108.      * 设置地图UI和定位样式
  109.      */
  110.     private void setupMapUI() {
  111.         try {
  112.             // 设置定位蓝点的样式
  113.             MyLocationStyle myLocationStyle = new MyLocationStyle();
  114.             // 连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动
  115.             myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);
  116.             // 设置定位蓝点的Style
  117.             myLocationStyle.strokeColor(Color.argb(180, 3, 145, 255));// 设置圆形的边框颜色
  118.             myLocationStyle.radiusFillColor(Color.argb(10, 0, 0, 180));// 设置圆形的填充颜色
  119.             myLocationStyle.strokeWidth(5.0f);// 设置圆形的边框粗细
  120.             // 设置定位蓝点的 Style
  121.             aMap.setMyLocationStyle(myLocationStyle);
  122.             // 设置定位监听
  123.             aMap.setOnMyLocationChangeListener(location -> {
  124.                 // 当位置发生变化时,将地图中心点移动到当前位置
  125.                 aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
  126.                         new com.amap.api.maps.model.LatLng(location.getLatitude(), location.getLongitude()),
  127.                         17)); // 设置缩放级别为17
  128.             });
  129.         } catch (Exception e) {
  130.             e.printStackTrace();
  131.             Toast.makeText(this, "设置地图UI失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  132.         }
  133.     }
  134.     /**
  135.      * 初始化导航和路线搜索
  136.      */
  137.     private void initNaviAndRoute() {
  138.         try {
  139.             // 初始化路线规划
  140.             routeSearch = new RouteSearch(this);
  141.             routeSearch.setRouteSearchListener(this);
  142.             // 初始化导航
  143.             mAMapNavi = AMapNavi.getInstance(getApplicationContext());
  144.             mAMapNavi.addAMapNaviListener(this);
  145.             // 设置地图点击监听,用于选择目的地
  146.             aMap.setOnMapClickListener(latLng -> {
  147.                 try {
  148.                     // 清除之前的标记
  149.                     if (destinationMarker != null) {
  150.                         destinationMarker.remove();
  151.                     }
  152.                     // 添加新的目的地标记
  153.                     MarkerOptions markerOptions = new MarkerOptions()
  154.                             .position(latLng)
  155.                             .title("目的地");
  156.                     destinationMarker = aMap.addMarker(markerOptions);
  157.                     // 开始路线规划
  158.                     planRoute(latLng);
  159.                 } catch (Exception e) {
  160.                     e.printStackTrace();
  161.                     Toast.makeText(this, "设置目的地失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  162.                 }
  163.             });
  164.         } catch (Exception e) {
  165.             e.printStackTrace();
  166.             Toast.makeText(this, "初始化导航失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  167.         }
  168.     }
  169.     /**
  170.      * 检查并请求定位权限
  171.      */
  172.     private void checkAndRequestPermissions() {
  173.         if (lacksPermissions(NEEDED_PERMISSIONS)) {
  174.             ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, LOCATION_PERMISSION_REQUEST_CODE);
  175.         } else {
  176.             // 已经有权限,启用定位图层
  177.             enableMyLocation();
  178.         }
  179.     }
  180.     /**
  181.      * 检查是否缺少权限
  182.      */
  183.     private boolean lacksPermissions(String... permissions) {
  184.         for (String permission : permissions) {
  185.             if (ContextCompat.checkSelfPermission(this, permission) !=
  186.                     PackageManager.PERMISSION_GRANTED) {
  187.                 return true;
  188.             }
  189.         }
  190.         return false;
  191.     }
  192.     /**
  193.      * 处理权限请求结果
  194.      */
  195.     @Override
  196.     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
  197.             @NonNull int[] grantResults) {
  198.         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  199.         if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
  200.             if (grantResults.length > 0) {
  201.                 boolean allGranted = true;
  202.                 for (int grantResult : grantResults) {
  203.                     if (grantResult != PackageManager.PERMISSION_GRANTED) {
  204.                         allGranted = false;
  205.                         break;
  206.                     }
  207.                 }
  208.                 if (allGranted) {
  209.                     // 获得权限,启用定位图层
  210.                     enableMyLocation();
  211.                 } else {
  212.                     Toast.makeText(this, "需要定位权限才能显示当前位置", Toast.LENGTH_SHORT).show();
  213.                 }
  214.             }
  215.         }
  216.     }
  217.     /*启用定位图层*/
  218.     private void enableMyLocation() {
  219.         // 设置为true表示启动显示定位蓝点
  220.         aMap.setMyLocationEnabled(true);
  221.     }
  222.     @Override
  223.     protected void onResume() {
  224.         super.onResume();
  225.         mapView.onResume();
  226.     }
  227.     @Override
  228.     protected void onPause() {
  229.         super.onPause();
  230.         mapView.onPause();
  231.     }
  232.     @Override
  233.     protected void onSaveInstanceState(Bundle outState) {
  234.         super.onSaveInstanceState(outState);
  235.         mapView.onSaveInstanceState(outState);
  236.     }
  237.     @Override
  238.     protected void onDestroy() {
  239.         super.onDestroy();
  240.         try {
  241.             if (mapView != null) {
  242.                 mapView.onDestroy();
  243.             }
  244.             if (mAMapNavi != null) {
  245.                 mAMapNavi.destroy();
  246.             }
  247.         } catch (Exception e) {
  248.             e.printStackTrace();
  249.         }
  250.     }
  251.     @Override
  252.     public void onInitNaviFailure() {
  253.         // 导航初始化失败
  254.         Toast.makeText(this, "导航初始化失败", Toast.LENGTH_SHORT).show();
  255.     }
  256.     @Override
  257.     public void onInitNaviSuccess() {
  258.         // 导航初始化成功
  259.         Toast.makeText(this, "导航初始化成功", Toast.LENGTH_SHORT).show();
  260.     }
  261.     @Override
  262.     public void onStartNavi(int type) {
  263.         // 开始导航回调
  264.         Toast.makeText(this, "开始导航", Toast.LENGTH_SHORT).show();
  265.     }
  266.     @Override public void onTrafficStatusUpdate() {}
  267.     @Override
  268.     public void onLocationChange(AMapNaviLocation location) {
  269.         // 导航过程中位置变化
  270.         }
  271.     @Override public void onGetNavigationText(int i, String s) {}
  272.     @Override public void onGetNavigationText(String s) {}
  273.     @Override public void onEndEmulatorNavi() {}
  274.     @Override
  275.     public void onArriveDestination() {
  276.         // 到达目的地
  277.         Toast.makeText(this, "到达目的地", Toast.LENGTH_SHORT).show();
  278.     }
  279.     @Override public void onCalculateRouteFailure(int i) {}
  280.     @Override public void onReCalculateRouteForYaw() {}
  281.     @Override public void onReCalculateRouteForTrafficJam() {}
  282.     @Override
  283.     public void onArrivedWayPoint(int i) {}
  284.     @Override public void onGpsOpenStatus(boolean b) {}
  285.     @Override public void onNaviInfoUpdate(NaviInfo naviInfo) {}
  286.     @Override
  287.     public void updateCameraInfo(AMapNaviCameraInfo[] aMapNaviCameraInfos) {}
  288.     @Override
  289.     public void updateIntervalCameraInfo(AMapNaviCameraInfo aMapNaviCameraInfo, AMapNaviCameraInfo aMapNaviCameraInfo1, int i) {}
  290.     @Override public void onServiceAreaUpdate(AMapServiceAreaInfo[] aMapServiceAreaInfos) {}
  291.     @Override public void showCross(AMapNaviCross aMapNaviCross) {}
  292.     @Override public void hideCross() {}
  293.     @Override public void showModeCross(AMapModelCross aMapModelCross) {}
  294.     @Override public void hideModeCross() {}
  295.     @Override public void showLaneInfo(AMapLaneInfo[] aMapLaneInfos, byte[] bytes, byte[] bytes1) {}
  296.     @Override public void showLaneInfo(AMapLaneInfo aMapLaneInfo) {}
  297.     @Override public void hideLaneInfo() {}
  298.     @Override public void onCalculateRouteSuccess(int[] ints) {}
  299.     @Override public void notifyParallelRoad(int i) {}
  300.     @Override public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo[] aMapNaviTrafficFacilityInfos) {}
  301.     @Override public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo aMapNaviTrafficFacilityInfo) {}
  302.     @Override public void updateAimlessModeStatistics(AimLessModeStat aimLessModeStat) {}
  303.     @Override public void updateAimlessModeCongestionInfo(AimLessModeCongestionInfo aimLessModeCongestionInfo) {}
  304.     @Override public void onPlayRing(int i) {}
  305.     @Override
  306.     public void onCalculateRouteSuccess(AMapCalcRouteResult result) {
  307.         Toast.makeText(this, "路线规划成功", Toast.LENGTH_SHORT).show();
  308.         AMapNaviPath path = mAMapNavi.getNaviPath();
  309.         if (path != null) {
  310.             // 更新底部面板信息
  311.             updateBottomSheetInfo(path);
  312.             // 绘制路线
  313.             drawRouteLine(path);
  314.         }
  315.     }
  316.     @Override
  317.     public void onCalculateRouteFailure(AMapCalcRouteResult result) {
  318.         // 路线规划失败
  319.         Toast.makeText(this, "路线规划失败:" + result.getErrorDetail(), Toast.LENGTH_SHORT).show();
  320.     }
  321.      @Override public void onNaviRouteNotify(AMapNaviRouteNotifyData aMapNaviRouteNotifyData) {}
  322.     @Override public void onGpsSignalWeak(boolean b) {}
  323.     @Override public void onBusRouteSearched(BusRouteResult result, int errorCode) {}
  324.     @Override public void onDriveRouteSearched(DriveRouteResult result, int errorCode) {}
  325.     @Override public void onWalkRouteSearched(WalkRouteResult result, int errorCode) {}
  326.     @Override public void onRideRouteSearched(RideRouteResult result, int errorCode) {}
  327.     private void planRoute(LatLng destination) {
  328.         try {
  329.             if (aMap.getMyLocation() == null) {
  330.                 Toast.makeText(this, "等待定位信息...", Toast.LENGTH_SHORT).show();
  331.                 return;
  332.             }
  333.             // 获取起点(当前位置)
  334.             LatLonPoint startPoint = new LatLonPoint(
  335.                     aMap.getMyLocation().getLatitude(),
  336.                     aMap.getMyLocation().getLongitude()
  337.             );
  338.             // 终点
  339.             LatLonPoint endPoint = new LatLonPoint(destination.latitude, destination.longitude);
  340.             // 构建导航数据
  341.             List<NaviLatLng> startList = new ArrayList<>();
  342.             List<NaviLatLng> endList = new ArrayList<>();
  343.             startList.add(new NaviLatLng(startPoint.getLatitude(), startPoint.getLongitude()));
  344.             endList.add(new NaviLatLng(endPoint.getLatitude(), endPoint.getLongitude()));
  345.             // 开始算路
  346.             mAMapNavi.calculateDriveRoute(startList, endList, null, 2);
  347.         } catch (Exception e) {
  348.             e.printStackTrace();
  349.             Toast.makeText(this, "路线规划失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  350.         }
  351.     }
  352.     private void initBottomSheet() {
  353.         try {
  354.             View bottomSheet = findViewById(R.id.bottom_sheet);
  355.             bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
  356.             
  357.             // 设置初始状态为折叠
  358.             bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
  359.             
  360.             // 初始化视图
  361.             tvDestination = findViewById(R.id.tv_destination);
  362.             tvDistance = findViewById(R.id.tv_distance);
  363.             tvTime = findViewById(R.id.tv_time);
  364.             btnStartNavi = findViewById(R.id.btn_start_navi);
  365.             // 初始化搜索框
  366.             EditText searchInput = findViewById(R.id.search_input);
  367.             searchInput.setOnEditorActionListener((v, actionId, event) -> {
  368.                 if (actionId == EditorInfo.IME_ACTION_SEARCH) {
  369.                     String keyword = searchInput.getText().toString().trim();
  370.                     if (!TextUtils.isEmpty(keyword)) {
  371.                         // 隐藏键盘
  372.                         InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
  373.                         imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0);
  374.                         
  375.                         // 开始搜索
  376.                         searchLocation(keyword);
  377.                         return true;
  378.                     }
  379.                 }
  380.                 return false;
  381.             });
  382.             // 设置底部面板的状态改变监听
  383.             bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
  384.                 @Override
  385.                 public void onStateChanged(@NonNull View bottomSheet, int newState) {
  386.                     switch (newState) {
  387.                         case BottomSheetBehavior.STATE_COLLAPSED:
  388.                             // 面板折叠状态
  389.                             break;
  390.                         case BottomSheetBehavior.STATE_EXPANDED:
  391.                             // 面板展开状态
  392.                             break;
  393.                     }
  394.                 }
  395.                 @Override
  396.                 public void onSlide(@NonNull View bottomSheet, float slideOffset) {
  397.                     // 滑动时的处理
  398.                 }
  399.             });
  400.             // 设置开始导航按钮点击事件
  401.             btnStartNavi.setOnClickListener(v -> {
  402.                 if (mAMapNavi != null) {
  403.                     mAMapNavi.startNavi(1); // 1表示真实导航
  404.                     bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
  405.                 }
  406.             });
  407.         } catch (Exception e) {
  408.             e.printStackTrace();
  409.             Toast.makeText(this, "初始化底部面板失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  410.         }
  411.     }
  412.     /**搜索位置*/
  413.     private void searchLocation(String keyword) {
  414.         try {
  415.             Toast.makeText(this, "正在搜索: " + keyword, Toast.LENGTH_SHORT).show();
  416.             
  417.             // 创建POI搜索对象
  418.             PoiSearch.Query query = new PoiSearch.Query(keyword, "", "全国");  // 设置搜索范围为全国
  419.             query.setPageSize(10); // 设置每页最多返回多少条数据
  420.             query.setPageNum(1);   // 设置查询第几页,从1开始
  421.             PoiSearch poiSearch = new PoiSearch(this, query);
  422.             poiSearch.setOnPoiSearchListener(this);
  423.             
  424.             // 如果当前位置可用,设置搜索中心点
  425.             if (aMap != null && aMap.getMyLocation() != null) {
  426.                 poiSearch.setBound(new PoiSearch.SearchBound(
  427.                     new LatLonPoint(aMap.getMyLocation().getLatitude(),
  428.                                   aMap.getMyLocation().getLongitude()),
  429.                     50000));  // 设置搜索半径为50公里
  430.             }
  431.             
  432.             poiSearch.searchPOIAsyn(); // 开始异步搜索
  433.         } catch (AMapException e) {
  434.             e.printStackTrace();
  435.             Toast.makeText(this, "搜索初始化失败:" + e.getErrorMessage(), Toast.LENGTH_SHORT).show();
  436.         } catch (Exception e) {
  437.             e.printStackTrace();
  438.             Toast.makeText(this, "搜索失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
  439.         }
  440.     }
  441.     @Override
  442.     public void onPoiSearched(PoiResult result, int rCode) {
  443.         if (rCode == AMapException.CODE_AMAP_SUCCESS) {
  444.             if (result != null && result.getPois() != null && !result.getPois().isEmpty()) {
  445.                 // 获取第一个POI点
  446.                 PoiItem poi = result.getPois().get(0);
  447.                 // 更新目的地信息
  448.                 updateDestinationInfo(poi);
  449.                 // 移动地图到搜索位置
  450.                 moveMapToLocation(poi.getLatLonPoint());
  451.                 // 规划路线
  452.                 planRouteToDestination(poi.getLatLonPoint());
  453.             } else {
  454.                 Toast.makeText(this, "未找到相关地点,请尝试其他关键词", Toast.LENGTH_SHORT).show();
  455.             }
  456.         } else {
  457.             String errorMessage;
  458.             switch (rCode) {
  459.                 case AMapException.CODE_AMAP_ENGINE_RESPONSE_ERROR:
  460.                     errorMessage = "服务响应错误";
  461.                     break;
  462.                 case AMapException.CODE_AMAP_ENGINE_CONNECT_TIMEOUT:
  463.                     errorMessage = "连接超时";
  464.                     break;
  465.                 case AMapException.CODE_AMAP_ENGINE_RETURN_TIMEOUT:
  466.                     errorMessage = "请求超时";
  467.                     break;
  468.                 default:
  469.                     errorMessage = "未知错误(错误码:" + rCode + ")";
  470.             }
  471.             Toast.makeText(this, "搜索失败:" + errorMessage, Toast.LENGTH_SHORT).show();
  472.         }
  473.     }
  474.     @Override
  475.     public void onPoiItemSearched(PoiItem poiItem, int rCode) {
  476.         // 处理单个POI搜索结果
  477.     }
  478.     private void updateDestinationInfo(PoiItem poi) {
  479.         tvDestination.setText("目的地:" + poi.getTitle());
  480.         // 保存目的地信息,用于后续导航
  481.         destinationPoint = poi.getLatLonPoint();
  482.         // 展开面板
  483.         bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
  484.     }
  485.     private void moveMapToLocation(LatLonPoint point) {
  486.         if (aMap != null) {
  487.             // 清除之前的标记
  488.             aMap.clear();
  489.             // 添加新的标记
  490.             LatLng latLng = new LatLng(point.getLatitude(), point.getLongitude());
  491.             aMap.addMarker(new MarkerOptions()
  492.                     .position(latLng)
  493.                     .title("目的地"));
  494.             // 移动地图
  495.             aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));
  496.         }
  497.     }
  498.     private void planRouteToDestination(LatLonPoint destination) {
  499.         if (aMap.getMyLocation() == null) {
  500.             Toast.makeText(this, "无法获取当前位置", Toast.LENGTH_SHORT).show();
  501.             return;
  502.         }
  503.         // 构建导航起终点
  504.         List<NaviLatLng> startList = new ArrayList<>();
  505.         List<NaviLatLng> endList = new ArrayList<>();
  506.         // 起点为当前位置
  507.         startList.add(new NaviLatLng(aMap.getMyLocation().getLatitude(),
  508.                 aMap.getMyLocation().getLongitude()));
  509.         // 终点为搜索的位置
  510.         endList.add(new NaviLatLng(destination.getLatitude(),
  511.                 destination.getLongitude()));
  512.         // 开始算路
  513.         mAMapNavi.calculateDriveRoute(startList, endList, null,
  514.                 PathPlanningStrategy.DRIVING_DEFAULT);
  515.     }
  516.     private void drawRouteLine(AMapNaviPath path) {
  517.         if (aMap != null) {
  518.             // 清除之前的路线
  519.             aMap.clear();
  520.             // 绘制新路线
  521.             AMapNaviPath naviPath = mAMapNavi.getNaviPath();
  522.             if (naviPath != null) {
  523.                 // 获取路线坐标点
  524.                 List<NaviLatLng> coordinates = naviPath.getCoordList();
  525.                 if (coordinates != null && coordinates.size() > 0) {
  526.                     List<LatLng> latLngs = new ArrayList<>();
  527.                     for (NaviLatLng coordinate : coordinates) {
  528.                         latLngs.add(new LatLng(coordinate.getLatitude(), coordinate.getLongitude()));
  529.                     }
  530.                     // 绘制路线
  531.                     aMap.addPolyline(new PolylineOptions()
  532.                             .addAll(latLngs)
  533.                             .width(20)
  534.                             .color(Color.BLUE));
  535.                     
  536.                     // 添加起点和终点标记
  537.                     LatLng startPoint = latLngs.get(0);
  538.                     LatLng endPoint = latLngs.get(latLngs.size() - 1);
  539.                     
  540.                     // 添加起点标记
  541.                     aMap.addMarker(new MarkerOptions()
  542.                             .position(startPoint)
  543.                             .title("起点"));
  544.                     
  545.                     // 添加终点标记
  546.                     aMap.addMarker(new MarkerOptions()
  547.                             .position(endPoint)
  548.                             .title("终点"));
  549.                     // 调整地图视野以显示整条路线
  550.                     LatLngBounds bounds = new LatLngBounds.Builder()
  551.                             .include(startPoint)
  552.                             .include(endPoint)
  553.                             .build();
  554.                     aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
  555.                 }
  556.             }
  557.         }
  558.     }
  559.     private void updateBottomSheetInfo(AMapNaviPath path) {
  560.         if (path != null) {
  561.             // 更新距离信息(转换为公里)
  562.             String distance = String.format("距离:%.1f公里", path.getAllLength() / 1000.0);
  563.             tvDistance.setText(distance);
  564.             // 更新时间信息(转换为分钟)
  565.             String time = String.format("预计用时:%d分钟", path.getAllTime() / 60);
  566.             tvTime.setText(time);
  567.             // 如果有目的地标记,更新目的地信息
  568.             if (destinationMarker != null) {
  569.                 String destination = "目的地:" + destinationMarker.getTitle();
  570.                 tvDestination.setText(destination);
  571.             }
  572.         }
  573.     }
  574. }
复制代码
XML文件
  1. <androidx.coordinatorlayout.widget.CoordinatorLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:fitsSystemWindows="true"    tools:context=".MainActivity">    <!-- 地图容器 -->    <com.amap.api.maps.MapView        android:id="@+id/map"        android:layout_width="match_parent"        android:layout_height="match_parent" />    <!-- 底部导航面板 -->    <LinearLayout        android:id="@+id/bottom_sheet"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="#F5FFFFFF"        android:orientation="vertical"        android:elevation="8dp"        app:behavior_peekHeight="180dp"        app:behavior_hideable="false"        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">        <!-- 拖动条指示器 -->        <View            android:layout_width="40dp"            android:layout_height="4dp"            android:layout_gravity="center_horizontal"            android:layout_marginTop="8dp"            android:layout_marginBottom="16dp"            android:background="#CCCCCC" />        <!-- 搜索框区域 -->
  2.         <LinearLayout
  3.             android:layout_width="match_parent"
  4.             android:layout_height="wrap_content"
  5.             android:orientation="horizontal"
  6.             android:padding="8dp"
  7.             android:layout_marginHorizontal="16dp"
  8.             android:layout_marginBottom="8dp"
  9.             android:background="@drawable/search_background"
  10.             android:elevation="2dp">
  11.             <!-- 搜索图标 -->
  12.             <ImageView
  13.                 android:id="@+id/search_icon"
  14.                 android:layout_width="24dp"
  15.                 android:layout_height="24dp"
  16.                 android:layout_gravity="center_vertical"
  17.                 android:layout_marginStart="8dp"
  18.                 android:src="@android:drawable/ic_search_category_default"
  19.                 app:tint="#666666" />
  20.             <!-- 搜索输入框 -->
  21.             <EditText
  22.                 android:id="@+id/search_input"
  23.                 android:layout_width="0dp"
  24.                 android:layout_height="40dp"
  25.                 android:layout_weight="1"
  26.                 android:background="@null"
  27.                 android:hint="搜索目的地"
  28.                 android:textColorHint="#999999"
  29.                 android:textColor="#333333"
  30.                 android:textSize="16sp"
  31.                 android:singleLine="true"
  32.                 android:imeOptions="actionSearch"
  33.                 android:layout_marginStart="8dp"
  34.                 android:layout_marginEnd="8dp" />
  35.         </LinearLayout>
  36.         <!-- 导航信息区域 -->        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:orientation="vertical"            android:padding="16dp">            <!-- 目的地信息 -->            <TextView                android:id="@+id/tv_destination"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginBottom="8dp"                android:text="目的地:"                android:textColor="#333333"                android:textSize="16sp"                android:textStyle="bold" />            <!-- 距离信息 -->            <TextView                android:id="@+id/tv_distance"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginBottom="8dp"                android:text="距离:"                android:textColor="#666666"                android:textSize="16sp" />            <!-- 预计时间 -->            <TextView                android:id="@+id/tv_time"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginBottom="8dp"                android:text="预计时间:"                android:textColor="#666666"                android:textSize="16sp" />            <!-- 导航按钮 -->            <Button                android:id="@+id/btn_start_navi"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginTop="16dp"                android:background="@android:color/holo_blue_light"                android:elevation="4dp"                android:padding="12dp"                android:text="开始导航"                android:textColor="#FFFFFF"                android:textSize="16sp" />        </LinearLayout>    </LinearLayout></androidx.coordinatorlayout.widget.CoordinatorLayout>
复制代码
末了实现的效果:

3、处理惩罚偏航

①onReCalculateRouteForYaw 方法(核心偏航处理惩罚方法)

  1. @Override
  2. public void onReCalculateRouteForYaw() {
  3.     // 偏航重新计算路线回调
  4.     requireActivity().runOnUiThread(() -> {
  5.         String message = "您已偏离规划路线,正在重新规划";
  6.         Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
  7.         if (!isMuted && textToSpeech != null) {
  8.             textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
  9.         }
  10.     });
  11. }
复制代码
②、calculateRoute 方法(门路重新规划)

  1. private void calculateRoute() {
  2.     if (mStartPoint == null) {
  3.         // 如果没有起点,使用当前位置
  4.         Location location = aMap.getMyLocation();
  5.         if (location != null) {
  6.             mStartPoint = new LatLng(location.getLatitude(), location.getLongitude());
  7.         } else {
  8.             Toast.makeText(requireContext(), "无法获取当前位置", Toast.LENGTH_SHORT).show();
  9.             return;
  10.         }
  11.     }
  12.     try {
  13.         // 清除之前的路线
  14.         aMap.clear();
  15.         // 准备导航起终点
  16.         List<NaviLatLng> startPoints = new ArrayList<>();
  17.         startPoints.add(new NaviLatLng(mStartPoint.latitude, mStartPoint.longitude));
  18.         List<NaviLatLng> endPoints = new ArrayList<>();
  19.         endPoints.add(new NaviLatLng(mEndPoint.latitude, mEndPoint.longitude));
  20.         // 根据出行方式计算路线
  21.         switch (currentNaviMode) {
  22.             case 0: // 驾车
  23.                 mAMapNavi.calculateDriveRoute(startPoints, endPoints, null,
  24.                     PathPlanningStrategy.DRIVING_DEFAULT);
  25.                 break;
  26.   
  27.         }
  28.     } catch (Exception e) {
  29.         e.printStackTrace();
  30.         Toast.makeText(requireContext(), "路线规划失败:" + e.getMessage(),
  31.             Toast.LENGTH_SHORT).show();
  32.     }
  33. }
复制代码
③、onCalculateRouteSuccess 方法(门路重新规划乐成回调)

  1. @Override
  2. public void onCalculateRouteSuccess(AMapCalcRouteResult result) {
  3.     if (result != null) {
  4.         try {
  5.             // 清除之前的标记和路线
  6.             aMap.clear();
  7.             mapPolylines.clear();
  8.             // 添加起点和终点标记
  9.             if (mStartPoint != null) {
  10.                 MarkerOptions startMarker = new MarkerOptions()
  11.                     .position(mStartPoint)
  12.                     .title("起点")
  13.                     .snippet("当前位置")
  14.                     .icon(BitmapDescriptorFactory.defaultMarker(
  15.                         BitmapDescriptorFactory.HUE_GREEN));
  16.                 aMap.addMarker(startMarker);
  17.             }
  18.             if (mEndPoint != null) {
  19.                 MarkerOptions endMarker = new MarkerOptions()
  20.                     .position(mEndPoint)
  21.                     .title("终点")
  22.                     .snippet("目的地")
  23.                     .icon(BitmapDescriptorFactory.defaultMarker(
  24.                         BitmapDescriptorFactory.HUE_RED));
  25.                 aMap.addMarker(endMarker);
  26.             }
  27.             // 获取所有路线ID并绘制路线
  28.             int[] routeIds = result.getRouteid();
  29.             if (routeIds != null && routeIds.length > 0) {
  30.                 currentRouteIds = routeIds;
  31.                 updateRouteOptions();
  32.                 drawSelectedRoute();
  33.             }
  34.         } catch (Exception e) {
  35.             e.printStackTrace();
  36.             Toast.makeText(requireContext(), "路线显示失败:" + e.getMessage(),
  37.                 Toast.LENGTH_SHORT).show();
  38.         }
  39.     }
  40. }
复制代码
④、drawSelectedRoute 方法(绘制新门路)

  1. private void drawSelectedRoute() {
  2.     try {
  3.         // 选择路线
  4.         mAMapNavi.selectRouteId(currentRouteIds[selectedRouteIndex]);
  5.         // 清除之前的路线
  6.         for (Polyline line : mapPolylines) {
  7.             line.remove();
  8.         }
  9.         mapPolylines.clear();
  10.         // 获取路线信息
  11.         List<NaviLatLng> pathPoints = mAMapNavi.getNaviPath().getCoordList();
  12.         if (pathPoints != null && !pathPoints.isEmpty()) {
  13.             // 设置路线颜色
  14.             int pathColor;
  15.             switch (currentNaviMode) {
  16.                 case 1: // 步行
  17.                     pathColor = Color.GREEN;
  18.                     break;
  19.             }
  20.             // 转换坐标点并绘制路线
  21.             List<LatLng> mapPoints = new ArrayList<>();
  22.             for (NaviLatLng point : pathPoints) {
  23.                 mapPoints.add(new LatLng(point.getLatitude(), point.getLongitude()));
  24.             }
  25.             // 添加新路线
  26.             Polyline polyline = aMap.addPolyline(new PolylineOptions()
  27.                 .addAll(mapPoints)
  28.                 .width(20)
  29.                 .color(pathColor)
  30.                 .zIndex(1)
  31.                 .setDottedLine(false));
  32.             mapPolylines.add(polyline);
  33.             // 调整地图视野以显示整条路线
  34.             LatLngBounds.Builder builder = new LatLngBounds.Builder();
  35.             for (LatLng point : mapPoints) {
  36.                 builder.include(point);
  37.             }
  38.             aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(
  39.                 builder.build(), 100));
  40.         }
  41.     } catch (Exception e) {
  42.         e.printStackTrace();
  43.         Toast.makeText(requireContext(), "路线绘制失败:" + e.getMessage(),
  44.             Toast.LENGTH_SHORT).show();
  45.     }
  46. }
复制代码
五、其他功能

1、语音播报功能

① TextToSpeech 基本先容与用法

​ TextToSpeech 是 Android 提供的文字转语音引擎,可以将文本转换为语音输出。
1)初始化方式

  1. private TextToSpeech textToSpeech;
  2. // 方式一:基础初始化
  3. textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
  4.     @Override
  5.     public void onInit(int status) {
  6.         if (status == TextToSpeech.SUCCESS) {
  7.             // 设置语言
  8.             int result = textToSpeech.setLanguage(Locale.CHINESE);
  9.             
  10.             // 检查语言是否支持
  11.             if (result == TextToSpeech.LANG_MISSING_DATA ||
  12.                 result == TextToSpeech.LANG_NOT_SUPPORTED) {
  13.                 Toast.makeText(context, "语言不支持", Toast.LENGTH_SHORT).show();
  14.             }
  15.         } else {
  16.             Toast.makeText(context, "初始化失败", Toast.LENGTH_SHORT).show();
  17.         }
  18.     }
  19. });
  20. // 方式二:Lambda表达式(简化写法)
  21. textToSpeech = new TextToSpeech(requireContext(), status -> {
  22.     if (status == TextToSpeech.SUCCESS) {
  23.         textToSpeech.setLanguage(Locale.CHINESE);
  24.     }
  25. });
复制代码
2)主要配置方法

  1. // 设置语言
  2. textToSpeech.setLanguage(Locale.CHINESE);
  3. // 设置语速(1.0为正常速度)
  4. textToSpeech.setSpeechRate(1.0f);
  5. // 设置音调(1.0为正常音调)
  6. textToSpeech.setPitch(1.0f);
  7. // 设置音量(0.0-1.0)
  8. textToSpeech.setVolume(1.0f);
  9. // 设置音频流类型
  10. textToSpeech.setAudioAttributes(new AudioAttributes.Builder()
  11.     .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
  12.     .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
  13.     .build());
复制代码
3)语音播报方法

  1. // 1. 基本播报方法
  2. textToSpeech.speak(
  3.     "要播报的文本",           // 文本内容
  4.     TextToSpeech.QUEUE_FLUSH, // 播报模式
  5.     null,                    // Bundle参数
  6.     "utteranceId"           // 话语ID
  7. );
  8. // 2. 立即播报(清除队列)
  9. textToSpeech.speak(
  10.     "紧急提示信息",
  11.     TextToSpeech.QUEUE_FLUSH,
  12.     null,
  13.     null
  14. );
  15. // 3. 加入播报队列
  16. textToSpeech.speak(
  17.     "普通提示信息",
  18.     TextToSpeech.QUEUE_ADD,
  19.     null,
  20.     null
  21. );
  22. // 4. 带参数的播报
  23. Bundle params = new Bundle();
  24. params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, 0.8f);
  25. params.putFloat(TextToSpeech.Engine.KEY_PARAM_PITCH, 1.2f);
  26. textToSpeech.speak(
  27.     "自定义参数播报",
  28.     TextToSpeech.QUEUE_FLUSH,
  29.     params,
  30.     "customId"
  31. );
复制代码
4)播报状态监听

  1. textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
  2.     @Override
  3.     public void onStart(String utteranceId) {
  4.         // 开始播报
  5.     }
  6.     @Override
  7.     public void onDone(String utteranceId) {
  8.         // 播报完成
  9.     }
  10.     @Override
  11.     public void onError(String utteranceId) {
  12.         // 播报错误
  13.     }
  14. });
复制代码
5)权限要求

  1. <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
复制代码
②使用示例(步骤分析)

1). 初始化语音体系

  1. // 在类成员变量中声明
  2. private TextToSpeech textToSpeech;
  3. // 在 onCreateView 中初始化
  4. textToSpeech = new TextToSpeech(requireContext(), status -> {
  5.     if (status == TextToSpeech.SUCCESS) {
  6.         // 设置语音为中文
  7.         textToSpeech.setLanguage(Locale.CHINESE);
  8.     }
  9. });
复制代码
2).导航语音播报实现

  1. //开始导航
  2. private void startNavi() {
  3.     if (mStartPoint == null || mEndPoint == null) {
  4.         Toast.makeText(requireContext(), "请先规划路线", Toast.LENGTH_SHORT).show();
  5.         return;
  6.     }
  7.     try {
  8.         mAMapNavi.selectRouteId(currentRouteIds[selectedRouteIndex]);
  9.         mAMapNavi.startNavi(NaviType.GPS);
  10.         isNavigating = true;
  11.         updateNavigationButtons(true);
  12.         showNavigationInfoPanel();
  13.         // 这里可以添加开始导航的语音提示
  14.         if (!isMuted && textToSpeech != null) {
  15.             textToSpeech.speak("开始导航", TextToSpeech.QUEUE_FLUSH, null, null);
  16.         }
  17.         
  18.         // ... 其他代码
  19.     } catch (Exception e) {
  20.         e.printStackTrace();
  21.     }
  22. }
  23. //导航过程中的语音提示:
  24. @Override
  25. public void onGetNavigationText(String text) {
  26.     try {
  27.         // 这是导航过程中的语音播报核心方法
  28.         if (!isMuted && textToSpeech != null && text != null) {
  29.             textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);
  30.         }
  31.     } catch (Exception e) {
  32.         e.printStackTrace();
  33.     }
  34. }
  35. @Override
  36. public void onGetNavigationText(int type, String text) {
  37.     onGetNavigationText(text);
  38. }
  39. // 到达目的地提醒
  40. @Override
  41. public void onArriveDestination() {
  42.     String message = "已到达目的地,本次导航结束";
  43.     Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
  44.     // 播放语音提醒
  45.     if (!isMuted && textToSpeech != null) {
  46.         textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
  47.     }
  48.     // 震动提醒
  49.     if (ContextCompat.checkSelfPermission(requireContext(),
  50.         android.Manifest.permission.VIBRATE) == PackageManager.PERMISSION_GRANTED) {
  51.         android.os.Vibrator vibrator = (android.os.Vibrator)
  52.             requireContext().getSystemService(Context.VIBRATOR_SERVICE);
  53.         if (vibrator != null && vibrator.hasVibrator()) {
  54.             vibrator.vibrate(500); // 震动500毫秒
  55.         }
  56.     }
  57. }
复制代码
3). 特殊情况的语音提醒(按需选择)

  1. // 偏航重新规划提醒
  2. @Override
  3. public void onReCalculateRouteForYaw() {
  4.     requireActivity().runOnUiThread(() -> {
  5.         String message = "您已偏离规划路线,正在重新规划";
  6.         Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
  7.         if (!isMuted && textToSpeech != null) {
  8.             textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
  9.         }
  10.     });
  11. }
  12. // 拥堵重新规划提醒
  13. @Override
  14. public void onReCalculateRouteForTrafficJam() {
  15.     requireActivity().runOnUiThread(() -> {
  16.         String message = "前方道路拥堵,正在重新规划路线";
  17.         Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
  18.         if (!isMuted && textToSpeech != null) {
  19.             textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
  20.         }
  21.     });
  22. }
复制代码
4). 交通办法语音提醒

  1. @Override
  2. public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo facility) {
  3.     if (facility == null || !isTrafficEnabled) return;
  4.     String message = "";
  5.     switch (facility.getBroadcastType()) {
  6.         case 0:  // 测速摄像头
  7.             message = String.format("前方%d米有测速摄像头,限速%d公里/小时",
  8.                 facility.getDistance(), facility.getLimitSpeed());
  9.             break;
  10.         case 1:  // 监控摄像头
  11.             message = String.format("前方%d米有监控摄像头",
  12.                 facility.getDistance());
  13.             break;
  14.         case 2:  // 事故
  15.             message = String.format("前方%d米发生交通事故,请谨慎驾驶",
  16.                 facility.getDistance());
  17.             break;
  18.         case 3:  // 施工
  19.             message = String.format("前方%d米正在施工,请减速慢行",
  20.                 facility.getDistance());
  21.             break;
  22.         case 4:  // 交通拥堵
  23.             message = String.format("前方%d米出现拥堵,建议绕行",
  24.                 facility.getDistance());
  25.             break;
  26.     }
  27.     if (!TextUtils.isEmpty(message)) {
  28.         final String finalMessage = message;
  29.         requireActivity().runOnUiThread(() -> {
  30.             // 显示提示信息
  31.             Toast.makeText(requireContext(), finalMessage, Toast.LENGTH_LONG).show();
  32.             // 播放语音提醒
  33.             if (!isMuted && textToSpeech != null) {
  34.                 textToSpeech.speak(finalMessage, TextToSpeech.QUEUE_ADD, null, null);
  35.             }
  36.         });
  37.     }
  38. }
复制代码
5). 速率超限提醒

  1. private void showSpeedWarning(int currentSpeed) {
  2.     String speedWarning = String.format("当前速度%d公里/小时,请注意行车安全",
  3.         currentSpeed);
  4.     Toast.makeText(requireContext(), speedWarning, Toast.LENGTH_SHORT).show();
  5.     if (!isMuted && textToSpeech != null) {
  6.         textToSpeech.speak("您的车速较快,请注意行车安全",
  7.             TextToSpeech.QUEUE_ADD, null, null);
  8.     }
  9. }
复制代码
6). 资源释放

  1. @Override
  2. public void onDestroy() {
  3.     super.onDestroy();
  4.     if (textToSpeech != null) {
  5.         textToSpeech.stop();    // 停止播报
  6.         textToSpeech.shutdown(); // 关闭引擎
  7.     }
  8. }
复制代码
2、实时路况监控

①路况开关控制

  1. //MainActivity
  2. private ImageButton btnTraffic;
  3.     private boolean isTrafficEnabled = true;  // 默认开启实时路况
  4. // 初始化实时路况按钮
  5.         btnTraffic = rootView.findViewById(R.id.btn_traffic);
  6. // 实时路况按钮点击事件
  7. btnTraffic.setOnClickListener(v -> {
  8.     isTrafficEnabled = !isTrafficEnabled;
  9.     aMap.setTrafficEnabled(isTrafficEnabled);  // 设置地图是否显示实时路况
  10.     updateTrafficButton();
  11.     // 显示提示
  12.     String message = isTrafficEnabled ? "已开启实时路况" : "已关闭实时路况";
  13.     Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
  14. });
  15. // 更新路况按钮状态
  16. private void updateTrafficButton() {
  17.     btnTraffic.setSelected(isTrafficEnabled);
  18.     btnTraffic.setAlpha(isTrafficEnabled ? 1.0f : 0.5f);
  19. }
复制代码
XML文件
  1. <!-- 实时路况按钮 -->
  2. <ImageButton
  3.     android:id="@+id/btn_traffic"
  4.     android:layout_width="40dp"
  5.     android:layout_height="40dp"
  6.     android:layout_margin="8dp"
  7.     android:background="@drawable/bg_map_button"
  8.     android:contentDescription="@string/traffic_button"
  9.     android:src="@drawable/ic_traffic" />
复制代码
按钮样式: res/drawable/bg_map_button.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3.     <item android:state_pressed="true">
  4.         <shape android:shape="oval">
  5.             <solid android:color="#E0E0E0" />
  6.         </shape>
  7.     </item>
  8.     <item android:state_selected="true">
  9.         <shape android:shape="oval">
  10.             <solid android:color="#E3F2FD" />
  11.         </shape>
  12.     </item>
  13.     <item>
  14.         <shape android:shape="oval">
  15.             <solid android:color="#FFFFFF" />
  16.         </shape>
  17.     </item>
  18. </selector>
复制代码
路况图标样式:res/drawable/ic_traffic.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3.     <item android:state_selected="true"
  4.         android:drawable="@drawable/ic_traffic_on" />
  5.     <item
  6.         android:drawable="@drawable/ic_traffic_off" />
  7. </selector>
复制代码
字符串资源:在values/strings
  1. <string name="traffic_button">实时路况</string>
复制代码
②路况状态更新

  1. @Override
  2. public void onTrafficStatusUpdate() {
  3.     // 路况更新时的处理
  4.     if (isTrafficEnabled) {
  5.         requireActivity().runOnUiThread(() -> {
  6.             // 可以在这里更新UI
  7.         });
  8.     }
  9. }
  10. @Override
  11. public void onNaviInfoUpdate(NaviInfo naviInfo) {
  12.     if (isNavigating && navigationInfoPanel.getVisibility() == View.VISIBLE && naviInfo != null) {
  13.         requireActivity().runOnUiThread(() -> {
  14.             // 更新路况状态(根据当前速度判断)
  15.             int currentSpeed = naviInfo.getCurrentSpeed();
  16.             String trafficText;
  17.             int trafficColor;
  18.             // 根据当前速度判断路况状态
  19.             if (currentSpeed < 15) { // 低于15km/h认为是拥堵
  20.                 trafficText = "拥堵";
  21.                 trafficColor = Color.parseColor("#F44336"); // 红色
  22.             } else if (currentSpeed < 30) { // 低于30km/h认为是缓行
  23.                 trafficText = "缓行";
  24.                 trafficColor = Color.parseColor("#FF9800"); // 橙色
  25.             } else { // 高于30km/h认为是畅通
  26.                 trafficText = "畅通";
  27.                 trafficColor = Color.parseColor("#4CAF50"); // 绿色
  28.             }
  29.             tvTrafficStatus.setText("当前路况:" + trafficText);
  30.             tvTrafficStatus.setTextColor(trafficColor);
  31.         });
  32.     }
  33. }
复制代码
③拥堵重新规划

  1. @Override
  2. public void onReCalculateRouteForTrafficJam() {
  3.     requireActivity().runOnUiThread(() -> {
  4.         String message = "前方道路拥堵,正在重新规划路线";
  5.         Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
  6.         if (!isMuted && textToSpeech != null) {
  7.             textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
  8.         }
  9.     });
  10. }
复制代码
3、安全提醒体系

①OnUpdateTrafficFacility 方法(交通办法安全提醒)

  1. @Override
  2. public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo[] facilities) {
  3.     // 批量交通设施提醒
  4.     if (facilities == null || facilities.length == 0 || !isTrafficEnabled) return;
  5.     for (AMapNaviTrafficFacilityInfo facility : facilities) {
  6.         String message = "";
  7.         switch (facility.getBroadcastType()) {
  8.             case 0:  // 测速摄像头
  9.                 message = String.format("前方%d米有测速摄像头,限速%d公里/小时",
  10.                     facility.getDistance(), facility.getLimitSpeed());
  11.                 break;
  12.             case 1:  // 监控摄像头
  13.                 message = String.format("前方%d米有监控摄像头", facility.getDistance());
  14.                 break;
  15.             case 2:  // 事故
  16.                 message = String.format("前方%d米发生交通事故,请谨慎驾驶", facility.getDistance());
  17.                 break;
  18.             case 3:  // 施工
  19.                 message = String.format("前方%d米正在施工,请减速慢行", facility.getDistance());
  20.                 break;
  21.             case 4:  // 交通拥堵
  22.                 message = String.format("前方%d米出现拥堵,建议绕行", facility.getDistance());
  23.                 break;
  24.         }
  25.         // 发送提醒
  26.         if (!TextUtils.isEmpty(message)) {
  27.             final String finalMessage = message;
  28.             requireActivity().runOnUiThread(() -> {
  29.                 // 显示提示信息
  30.                 Toast.makeText(requireContext(), finalMessage, Toast.LENGTH_LONG).show();
  31.                 // 语音播报
  32.                 if (!isMuted && textToSpeech != null) {
  33.                     textToSpeech.speak(finalMessage, TextToSpeech.QUEUE_ADD, null, null);
  34.                 }
  35.             });
  36.         }
  37.     }
  38. }
复制代码
②onNaviInfoUpdate 方法中的速率监控

  1. @Override
  2. public void onNaviInfoUpdate(NaviInfo naviInfo) {
  3.     if (isNavigating && navigationInfoPanel.getVisibility() == View.VISIBLE && naviInfo != null) {
  4.         requireActivity().runOnUiThread(() -> {
  5.             // ... 其他代码 ...
  6.             // 速度监控
  7.             int currentSpeed = naviInfo.getCurrentSpeed();
  8.             if (currentSpeed > 80) {
  9.                 String speedWarning = String.format("当前速度%d公里/小时,请注意行车安全",
  10.                     currentSpeed);
  11.                 Toast.makeText(requireContext(), speedWarning, Toast.LENGTH_SHORT).show();
  12.                 if (!isMuted && textToSpeech != null) {
  13.                     textToSpeech.speak("您的车速较快,请注意行车安全",
  14.                         TextToSpeech.QUEUE_ADD, null, null);
  15.                 }
  16.             }
  17.         });
  18.     }
  19. }
复制代码
③getWarningMessage 方法(气候安全提醒)

  1. private String getWarningMessage(String weatherType) {
  2.     if (weatherType.contains("雨")) {
  3.         return "注意:当前降雨可能影响出行安全,请携带雨具,谨慎驾驶。";
  4.     } else if (weatherType.contains("雪")) {
  5.         return "注意:当前降雪可能导致路面结冰,建议减少出行。";
  6.     } else if (weatherType.contains("雾")) {
  7.         return "注意:空气质量较差,建议戴口罩出行。";
  8.     } else if (weatherType.contains("霾")) {
  9.         return "注意:空气质量较差,建议戴口罩出行。";
  10.     }
  11.     return "";
  12. }
复制代码
4、气候信息和预警

①获取高德气候服务KEY

大要的流程与上面获取高德地图KEY相同,只有一些渺小的差别:


将获取的KEY复制到AndroidManifest里

②调用气候服务的方法

1)updateWeatherInfo 方法(核心方法)

  1. private void updateWeatherInfo(LatLng destination) {
  2.     try {
  3.         // 清除旧的天气信息
  4.         clearWeatherInfo();
  5.         // 计算并显示距离
  6.         float distance = calculateDistance(null, destination);
  7.         if (distance > 0) {
  8.             final float finalDistance = distance;
  9.             requireActivity().runOnUiThread(() -> {
  10.                 tvDistance.setText(String.format("距离: %.1fkm", finalDistance));
  11.             });
  12.         }
  13.         // 使用逆地理编码获取城市名称
  14.         GeocodeSearch geocodeSearch = new GeocodeSearch(requireContext());
  15.         geocodeSearch.setOnGeocodeSearchListener(new GeocodeSearch.OnGeocodeSearchListener() {
  16.             @Override
  17.             public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
  18.                 if (rCode == AMapException.CODE_AMAP_SUCCESS) {
  19.                     // 获取城市名称
  20.                     String city = result.getRegeocodeAddress().getCity();
  21.                     if (TextUtils.isEmpty(city)) {
  22.                         city = result.getRegeocodeAddress().getDistrict();
  23.                     }
  24.                     // 使用城市名称查询天气
  25.                     WeatherSearchQuery query = new WeatherSearchQuery(
  26.                         city,
  27.                         WeatherSearchQuery.WEATHER_TYPE_LIVE
  28.                     );
  29.                     WeatherSearch weatherSearch = new WeatherSearch(requireContext());
  30.                     weatherSearch.setOnWeatherSearchListener(NavigationFragment.this);
  31.                     weatherSearch.setQuery(query);
  32.                     weatherSearch.searchWeatherAsyn();
  33.                 }
  34.             }
  35.         });
  36.     } catch (Exception e) {
  37.         e.printStackTrace();
  38.     }
  39. }
复制代码
2)onWeatherLiveSearched 方法(气候查询回调)

  1. @Override
  2. public void onWeatherLiveSearched(LocalWeatherLiveResult weatherLiveResult, int rCode) {
  3.     requireActivity().runOnUiThread(() -> {
  4.         if (rCode == AMapException.CODE_AMAP_SUCCESS) {
  5.             LocalWeatherLive weatherLive = weatherLiveResult.getLiveResult();
  6.             
  7.             // 更新天气信息UI
  8.             tvWeather.setText(String.format("%s %s°C",
  9.                 weatherLive.getWeather(),
  10.                 weatherLive.getTemperature()));
  11.             tvHumidity.setText(weatherLive.getHumidity() + "%");
  12.             tvWind.setText(String.format("%s风 %s级",
  13.                 weatherLive.getWindDirection(),
  14.                 weatherLive.getWindPower()));
  15.             // 更新空气质量
  16.             String airQuality = getAirQualityByWeather(weatherLive.getWeather());
  17.             tvAqi.setText("空气质量:" + airQuality);
  18.             tvAqi.setTextColor(getAirQualityColor(airQuality));
  19.             // 更新天气图标
  20.             updateWeatherIcon(weatherLive.getWeather());
  21.             // 生成出行建议
  22.             String recommendation = generateRecommendation(
  23.                 weatherLive.getWeather(),
  24.                 calculateDistance(mStartPoint, mEndPoint)
  25.             );
  26.             tvRecommendation.setText(recommendation);
  27.             // 显示天气警告
  28.             if (shouldShowWarning(weatherLive.getWeather())) {
  29.                 tvWeatherWarning.setVisibility(View.VISIBLE);
  30.                 tvWeatherWarning.setText(getWarningMessage(weatherLive.getWeather()));
  31.             }
  32.         }
  33.     });
  34. }
复制代码
3)getAirQualityByWeather 方法(获取氛围质量)

  1. private String getAirQualityByWeather(String weather) {
  2.     if (weather.contains("霾") || weather.contains("沙尘")) {
  3.         return "重度污染";
  4.     } else if (weather.contains("雾")) {
  5.         return "中度污染";
  6.     } else if (weather.contains("阴") || weather.contains("多云")) {
  7.         return "良";
  8.     } else if (weather.contains("雨") || weather.contains("雪")) {
  9.         return "良";
  10.     } else if (weather.contains("晴")) {
  11.         return "优";
  12.     } else {
  13.         return "良";
  14.     }
  15. }
  16. //获取空气质量对应颜色
  17. private int getAirQualityColor(String quality) {
  18.     switch (quality) {
  19.         case "优":
  20.             return Color.parseColor("#00C853");
  21.         case "良":
  22.             return Color.parseColor("#FFB300");
  23.         case "轻度污染":
  24.             return Color.parseColor("#FB8C00");
  25.         case "中度污染":
  26.             return Color.parseColor("#F4511E");
  27.         case "重度污染":
  28.             return Color.parseColor("#C62828");
  29.         case "严重污染":
  30.             return Color.parseColor("#6A1B9A");
  31.         default:
  32.             return Color.GRAY;
  33.     }
  34. }
复制代码
4)clearWeatherInfo 方法(扫除气候信息)

  1. private void clearWeatherInfo() {
  2.     requireActivity().runOnUiThread(() -> {
  3.         if (tvWeather != null) tvWeather.setText("正在获取天气...");
  4.         if (tvHumidity != null) tvHumidity.setText("");
  5.         if (tvWind != null) tvWind.setText("");
  6.         if (tvDistance != null) tvDistance.setText("");
  7.         if (tvRecommendation != null) tvRecommendation.setText("");
  8.         if (tvWeatherWarning != null) {
  9.             tvWeatherWarning.setText("");
  10.             tvWeatherWarning.setVisibility(View.GONE);
  11.         }
  12.     });
  13. }
复制代码
上述只是调用气候服务的方法,你还要创建一个显示气候信息的结构weather_info,例如:
  1.     <!-- 天气信息卡片 -->
  2.     <androidx.cardview.widget.CardView
  3.         android:layout_width="match_parent"
  4.         android:layout_height="wrap_content"
  5.         android:layout_marginBottom="16dp"
  6.         app:cardCornerRadius="12dp"
  7.         app:cardElevation="2dp">
  8.         <LinearLayout
  9.             android:layout_width="match_parent"
  10.             android:layout_height="wrap_content"
  11.             android:orientation="vertical"
  12.             android:padding="16dp">
  13.             <LinearLayout
  14.                 android:layout_width="match_parent"
  15.                 android:layout_height="wrap_content"
  16.                 android:orientation="horizontal"
  17.                 android:gravity="center_vertical">
  18.                 <ImageView
  19.                     android:id="@+id/weather_icon"
  20.                     android:layout_width="48dp"
  21.                     android:layout_height="48dp"
  22.                     android:src="@drawable/ic_weather_sunny"/>
  23.                 <LinearLayout
  24.                     android:layout_width="0dp"
  25.                     android:layout_height="wrap_content"
  26.                     android:layout_weight="1"
  27.                     android:layout_marginStart="16dp"
  28.                     android:orientation="vertical">
  29.                     <TextView
  30.                         android:id="@+id/tv_destination"
  31.                         android:layout_width="wrap_content"
  32.                         android:layout_height="wrap_content"
  33.                         android:textSize="16sp"
  34.                         android:textColor="#333333"
  35.                         android:textStyle="bold"
  36.                         android:text="目的地"/>
  37.                     <TextView
  38.                         android:id="@+id/tv_weather"
  39.                         android:layout_width="wrap_content"
  40.                         android:layout_height="wrap_content"
  41.                         android:layout_marginTop="4dp"
  42.                         android:textSize="14sp"
  43.                         android:textColor="#666666"
  44.                         android:text="晴天 26°C"/>
  45.                 </LinearLayout>
  46.                 <TextView
  47.                     android:id="@+id/tv_distance"
  48.                     android:layout_width="wrap_content"
  49.                     android:layout_height="wrap_content"
  50.                     android:textSize="14sp"
  51.                     android:textColor="#2196F3"
  52.                     android:text="距离: 5.2km"/>
  53.             </LinearLayout>
  54.             <View
  55.                 android:layout_width="match_parent"
  56.                 android:layout_height="1dp"
  57.                 android:layout_marginTop="12dp"
  58.                 android:layout_marginBottom="12dp"
  59.                 android:background="#E0E0E0"/>
  60.             <LinearLayout
  61.                 android:layout_width="match_parent"
  62.                 android:layout_height="wrap_content"
  63.                 android:orientation="horizontal">
  64.                 <LinearLayout
  65.                     android:layout_width="0dp"
  66.                     android:layout_height="wrap_content"
  67.                     android:layout_weight="1"
  68.                     android:orientation="vertical"
  69.                     android:gravity="center">
  70.                     <TextView
  71.                         android:id="@+id/tv_humidity"
  72.                         android:layout_width="wrap_content"
  73.                         android:layout_height="wrap_content"
  74.                         android:textSize="14sp"
  75.                         android:textColor="#333333"
  76.                         android:text="湿度"/>
  77.                     <TextView
  78.                         android:layout_width="wrap_content"
  79.                         android:layout_height="wrap_content"
  80.                         android:layout_marginTop="4dp"
  81.                         android:textSize="12sp"
  82.                         android:textColor="#666666"
  83.                         android:text="65%"/>
  84.                 </LinearLayout>
  85.                 <LinearLayout
  86.                     android:layout_width="0dp"
  87.                     android:layout_height="wrap_content"
  88.                     android:layout_weight="1"
  89.                     android:orientation="vertical"
  90.                     android:gravity="center">
  91.                     <TextView
  92.                         android:id="@+id/tv_wind"
  93.                         android:layout_width="wrap_content"
  94.                         android:layout_height="wrap_content"
  95.                         android:textSize="14sp"
  96.                         android:textColor="#333333"
  97.                         android:text="风速"/>
  98.                     <TextView
  99.                         android:layout_width="wrap_content"
  100.                         android:layout_height="wrap_content"
  101.                         android:layout_marginTop="4dp"
  102.                         android:textSize="12sp"
  103.                         android:textColor="#666666"
  104.                         android:text="3级"/>
  105.                 </LinearLayout>
  106.                 <LinearLayout
  107.                     android:layout_width="0dp"
  108.                     android:layout_height="wrap_content"
  109.                     android:layout_weight="1"
  110.                     android:orientation="vertical"
  111.                     android:gravity="center">
  112.                     <TextView
  113.                         android:id="@+id/tv_aqi"
  114.                         android:layout_width="wrap_content"
  115.                         android:layout_height="wrap_content"
  116.                         android:textSize="14sp"
  117.                         android:textColor="#333333"
  118.                         android:text="空气质量:优"/>
  119.                 </LinearLayout>
  120.             </LinearLayout>
  121.         </LinearLayout>
  122.     </androidx.cardview.widget.CardView>
复制代码
对控件进行初始化:
  1. // 在 onCreateView 方法中初始化天气信息相关的控件
  2. View weatherInfo = rootView.findViewById(R.id.weather_info);
  3. weatherIcon = weatherInfo.findViewById(R.id.weather_icon);
  4. tvDestination = weatherInfo.findViewById(R.id.tv_destination);
  5. tvWeather = weatherInfo.findViewById(R.id.tv_weather);
  6. tvDistance = weatherInfo.findViewById(R.id.tv_distance);
  7. tvHumidity = weatherInfo.findViewById(R.id.tv_humidity);
  8. tvWind = weatherInfo.findViewById(R.id.tv_wind);
  9. tvAqi = weatherInfo.findViewById(R.id.tv_aqi);
复制代码
气候信息面板的显示控制
  1. View weatherInfo = rootView.findViewById(R.id.weather_info);
  2.     weatherInfo.setVisibility(View.VISIBLE);  // 显示天气
  3.    //  weatherInfo.setVisibility(View.GONE);  // 隐藏天气
复制代码
5、智能出行建议

①、generateRecommendation 方法(核心方法)

  1. private String generateRecommendation(String weatherType, float distance) {
  2.     // 根据天气类型给出建议
  3.     if (weatherType.contains("暴雨") || weatherType.contains("大雨") || weatherType.contains("雷")) {
  4.         return "当前天气恶劣,建议选择驾车出行或推迟行程,注意道路积水和打滑。";
  5.     } else if (weatherType.contains("中雨") || weatherType.contains("小雨")) {
  6.         return "当前有降雨,建议携带雨具,选择驾车出行较为安全。";
  7.     } else if (weatherType.contains("雪")) {
  8.         return "当前降雪天气,路面可能结冰,建议驾车出行并保持低速行驶。";
  9.     } else if (weatherType.contains("雾") || weatherType.contains("霾")) {
  10.         return "当前能见度较低,建议戴好防护口罩,驾车出行时开启雾灯并保持安全车距。";
  11.     }
  12.    
  13.     // 根据天气和距离组合给出建议
  14.     else if (weatherType.contains("阴")) {
  15.         if (distance > 5) {
  16.             return "天气阴沉,距离较远,建议驾车出行。";
  17.         } else if (distance > 2) {
  18.             return "虽然天气阴沉,但温度适宜,可以考虑骑行或步行。";
  19.         } else {
  20.             return "距离较近,阴天适合运动,建议步行前往。";
  21.         }
  22.     } else if (weatherType.contains("晴")) {
  23.         if (distance > 5) {
  24.             return "天气晴朗,但距离较远,建议驾车出行。";
  25.         } else if (distance > 2) {
  26.             return "阳光明媚,距离适中,非常适合骑行,记得防晒。";
  27.         } else {
  28.             return "天气晴好,距离不远,建议步行,享受阳光。";
  29.         }
  30.     } else if (weatherType.contains("多云")) {
  31.         if (distance > 5) {
  32.             return "天气较好,但距离较远,建议驾车出行。";
  33.         } else if (distance > 2) {
  34.             return "云朵遮阳,温度舒适,是骑行的好天气。";
  35.         } else {
  36.             return "天气舒适,距离不远,非常适合步行。";
  37.         }
  38.     }
  39.    
  40.     // 默认建议
  41.     if (distance > 5) {
  42.         return "距离较远,建议选择驾车出行。";
  43.     } else if (distance > 2) {
  44.         return "距离适中,可以考虑骑行方式。";
  45.     } else {
  46.         return "距离较近,建议步行前往。";
  47.     }
  48. }
复制代码
②、calculateDistance 方法(盘算距离)

  1. private float calculateDistance(LatLng start, LatLng end) {
  2.     try {
  3.         if (end == null) {
  4.             return 0;
  5.         }
  6.         // 获取当前位置作为起点
  7.         Location currentLocation = aMap.getMyLocation();
  8.         if (currentLocation == null) {
  9.             if (start == null) {
  10.                 return 0;
  11.             }
  12.         } else {
  13.             // 使用当前实际位置作为起点
  14.             start = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
  15.         }
  16.         // 计算距离
  17.         if (start != null && end != null) {
  18.             float distance = AMapUtils.calculateLineDistance(start, end);
  19.             return distance / 1000.0f; // 转换为公里
  20.         }
  21.         return 0;
  22.     } catch (Exception e) {
  23.         e.printStackTrace();
  24.         return 0;
  25.     }
  26. }
复制代码
③、onWeatherLiveSearched 方法中的智能建议部分

  1. @Override
  2. public void onWeatherLiveSearched(LocalWeatherLiveResult weatherLiveResult, int rCode) {
  3.     requireActivity().runOnUiThread(() -> {
  4.         if (rCode == AMapException.CODE_AMAP_SUCCESS) {
  5.             LocalWeatherLive weatherLive = weatherLiveResult.getLiveResult();
  6.             
  7.             // 生成出行建议
  8.             String recommendation = generateRecommendation(
  9.                 weatherLive.getWeather(),
  10.                 calculateDistance(mStartPoint, mEndPoint)
  11.             );
  12.             tvRecommendation.setText(recommendation);
  13.             // 更新推荐的出行方式
  14.             updateRecommendedMode(weatherLive.getWeather(),
  15.                 calculateDistance(mStartPoint, mEndPoint));
  16.         }
  17.     });
  18. }
复制代码
总结:

上述的方法与代码仅供参考,当然这个项目并不完善,能修改的地方尚有很多,例如:我们还可以添加一个出行方式的选择,驾车、骑车、步行、公交等等,由于篇幅有限这里就不赘述了。
} else if (weatherType.contains(“晴”)) {
if (distance > 5) {
return “气候晴朗,但距离较远,建议驾车出行。”;
} else if (distance > 2) {
return “阳光妖冶,距离适中,非常得当骑行,记得防晒。”;
} else {
return “气候晴好,距离不远,建议步行,享受阳光。”;
}
} else if (weatherType.contains(“多云”)) {
if (distance > 5) {
return “气候较好,但距离较远,建议驾车出行。”;
} else if (distance > 2) {
return “云朵遮阳,温度舒适,是骑行的好气候。”;
} else {
return “气候舒适,距离不远,非常得当步行。”;
}
}
  1. // 默认建议
  2. if (distance > 5) {
  3.     return "距离较远,建议选择驾车出行。";
  4. } else if (distance > 2) {
  5.     return "距离适中,可以考虑骑行方式。";
  6. } else {
  7.     return "距离较近,建议步行前往。";
  8. }
复制代码
}
  1. ##### ②、calculateDistance 方法(盘算距离)```Javaprivate float calculateDistance(LatLng start, LatLng end) {
  2.     try {
  3.         if (end == null) {
  4.             return 0;
  5.         }
  6.         // 获取当前位置作为起点
  7.         Location currentLocation = aMap.getMyLocation();
  8.         if (currentLocation == null) {
  9.             if (start == null) {
  10.                 return 0;
  11.             }
  12.         } else {
  13.             // 使用当前实际位置作为起点
  14.             start = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
  15.         }
  16.         // 计算距离
  17.         if (start != null && end != null) {
  18.             float distance = AMapUtils.calculateLineDistance(start, end);
  19.             return distance / 1000.0f; // 转换为公里
  20.         }
  21.         return 0;
  22.     } catch (Exception e) {
  23.         e.printStackTrace();
  24.         return 0;
  25.     }
  26. }
复制代码
③、onWeatherLiveSearched 方法中的智能建议部分

  1. @Override
  2. public void onWeatherLiveSearched(LocalWeatherLiveResult weatherLiveResult, int rCode) {
  3.     requireActivity().runOnUiThread(() -> {
  4.         if (rCode == AMapException.CODE_AMAP_SUCCESS) {
  5.             LocalWeatherLive weatherLive = weatherLiveResult.getLiveResult();
  6.             
  7.             // 生成出行建议
  8.             String recommendation = generateRecommendation(
  9.                 weatherLive.getWeather(),
  10.                 calculateDistance(mStartPoint, mEndPoint)
  11.             );
  12.             tvRecommendation.setText(recommendation);
  13.             // 更新推荐的出行方式
  14.             updateRecommendedMode(weatherLive.getWeather(),
  15.                 calculateDistance(mStartPoint, mEndPoint));
  16.         }
  17.     });
  18. }
复制代码
总结:

上述的方法与代码仅供参考,当然这个项目并不完善,能修改的地方尚有很多,例如:我们还可以添加一个出行方式的选择,驾车、骑车、步行、公交等等,由于篇幅有限这里就不赘述了。
通过这个项目,对于高德地图SDK的使用,气候服务和数据处理惩罚,Android生命周期管理,异步编程和线程处理惩罚,错误处理惩罚和异常管理都有不小的劳绩,对于这些不认识的可以跟着做一下。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

嚴華

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表