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

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

项目实现了一个功能完整的导航系统,包含:
  1. 地图显示和控制
  2. 路线规划和导航
  3. 语音播报功能
  4. 实时路况监控
  5. 安全提醒系统
  6. 天气信息和预警
  7. 智能出行建议

一、高德地图KEY的申请

①、进入高德的控制台:https://tengyun-console.amap.com

②、完成注册后,点击右边的 应用管理→我的应用→创建新应用→获取KEY (不会的就跟着我下面的步骤来,包会的)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
如何获取自己的测试版安全码SHA1:

在这里插入图片描述

上述的命令:keytool -list -v -keystore debug.keystore

二、配置安卓相关权限和接入高德KEY

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

1、应用高德KEY

在AndroidManifest.xml中标签里面但不要在activity 标签下粘贴:

 <meta-data
            android:name="com.amap.api.v2.apikey"
            android:value="换成自己的高德KEY" />

2、配置权限

这些是可能会用到的权限,请在AndroidManifest.xml中粘贴(不要在标签里面粘贴,会报错):

<!-- 允许程序访问互联网 -->
<uses-permission android:name="android.permission.INTERNET" /> 

<!-- 允许程序设置内置sd卡的写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 

<!-- 允许程序获取网络状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 

<!-- 允许程序访问WiFi网络信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> 

<!-- 允许程序读写手机状态和身份 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> 

<!-- 允许程序访问CellID或WiFi热点来获取粗略的位置 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- 允许程序通过GPS芯片接收卫星的定位信息 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- 允许程序修改全局音频设置 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- 允许程序录制音频,用于语音识别功能 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />  

<!-- 允许程序控制手机振动器 -->
<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里的代码
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.amap.api.maps.AMap;
import com.amap.api.maps.MapView;
import com.amap.api.maps.MapsInitializer;
import com.amap.api.maps.model.MyLocationStyle;
import com.amap.api.maps.CameraUpdateFactory;

/**
 * 主活动类
 * 用于展示高德地图的主界面
 */
public class MainActivity extends AppCompatActivity {
    // 地图视图组件
    private MapView mapView;
    // 地图控制器对象
    private AMap aMap;
    // 定位权限请求码
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
    // 需要进行检测的权限数组
    private static final String[] NEEDED_PERMISSIONS = {
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 设置高德地图隐私政策
        // 更新同意隐私状态,需要在初始化地图之前完成
        //如果没有这两行代码,你的地图可能会不显示
        MapsInitializer.updatePrivacyShow(this, true, true);  // 展示隐私合规对话框
        MapsInitializer.updatePrivacyAgree(this, true);      // 同意隐私合规政策
        
        setContentView(R.layout.activity_main);
        
        // 初始化地图组件
        mapView = findViewById(R.id.map);
        // 必须回调MapView的onCreate方法,否则地图无法显示
        mapView.onCreate(savedInstanceState);
        
        // 初始化地图控制器
        // 只有在aMap为空时才重新获取,避免重复初始化
        if (aMap == null) {
            aMap = mapView.getMap();
            // 设置地图UI控件
            setupMapUI();
        }

        // 检查并请求定位权限
        checkAndRequestPermissions();
    }

    /**
     * 设置地图UI和定位样式
     */
    private void setupMapUI() {
        // 设置定位蓝点的样式
        MyLocationStyle myLocationStyle = new MyLocationStyle();
        // 连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动
        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);
        // 设置定位蓝点的Style
        myLocationStyle.strokeColor(Color.argb(180, 3, 145, 255));// 设置圆形的边框颜色
        myLocationStyle.radiusFillColor(Color.argb(10, 0, 0, 180));// 设置圆形的填充颜色
        myLocationStyle.strokeWidth(5.0f);// 设置圆形的边框粗细
        // 设置定位蓝点的 Style
        aMap.setMyLocationStyle(myLocationStyle);

        // 设置定位监听
        aMap.setOnMyLocationChangeListener(location -> {
            // 当位置发生变化时,将地图中心点移动到当前位置
            aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                    new com.amap.api.maps.model.LatLng(location.getLatitude(), location.getLongitude()), 
                    17)); // 设置缩放级别为17
        });
    }

    /**
     * 检查并请求定位权限
     */
    private void checkAndRequestPermissions() {
        if (lacksPermissions(NEEDED_PERMISSIONS)) {
            ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, LOCATION_PERMISSION_REQUEST_CODE);
        } else {
            // 已经有权限,启用定位图层
            enableMyLocation();
        }
    }

    /**
     * 检查是否缺少权限
     */
    private boolean lacksPermissions(String... permissions) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != 
                    PackageManager.PERMISSION_GRANTED) {
                return true;
            }
        }
        return false;
    }

    /**
     * 处理权限请求结果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0) {
                boolean allGranted = true;
                for (int grantResult : grantResults) {
                    if (grantResult != PackageManager.PERMISSION_GRANTED) {
                        allGranted = false;
                        break;
                    }
                }
                if (allGranted) {
                    // 获得权限,启用定位图层
                    enableMyLocation();
                } else {
                    Toast.makeText(this, "需要定位权限才能显示当前位置", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    /**
     * 启用定位图层
     */
    private void enableMyLocation() {
        // 设置为true表示启动显示定位蓝点
        aMap.setMyLocationEnabled(true);
    }

    /**
     * Activity生命周期方法 - 恢复
     * 必须回调MapView的onResume方法,否则地图会处于暂停状态
     */
    @Override
    protected void onResume() {
        super.onResume();
        mapView.onResume();
    }

    /**
     * Activity生命周期方法 - 暂停
     * 必须回调MapView的onPause方法,暂停地图的绘制
     */
    @Override
    protected void onPause() {
        super.onPause();
        mapView.onPause();
    }

    /**
     * Activity生命周期方法 - 保存状态
     * 保存地图当前的状态,在重建Activity时恢复状态
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

    /**
     * Activity生命周期方法 - 销毁
     * 必须回调MapView的onDestroy方法,销毁地图,释放资源
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
    }
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.amap.api.maps.MapView
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

通过上面的代码,我们能实现地图的导入,也成功的实现了这个项目的第一步(用真机进行测试,虚拟机会报错

在这里插入图片描述

2、实现路线规划和导航
①类定义和接口实现
public class MainActivity extends AppCompatActivity implements 
    RouteSearch.OnRouteSearchListener, // 路线搜索监听
    AMapNaviListener // 导航监听

写完以后会有这样的报错:那是因为接口里的方法没有写全

在这里插入图片描述

解决步骤:

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

在这里插入图片描述

在这里插入图片描述

这样Android就会帮我补全缺失的方法了

②创建一个可以拉动的半透明的面板

在res/layout下创建一个layout Resource File文件 名字叫 bottom_sheet_layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/bottom_sheet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#F5FFFFFF"
    android:orientation="vertical"
    android:padding="16dp"
    app:behavior_hideable="false"
    app:behavior_peekHeight="180dp"
    app:behavior_draggable="true"
    app:behavior_fitToContents="true"
    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_marginBottom="16dp"
        android:background="#CCCCCC" />

</LinearLayout> 
③创建一个搜索框

在res/drawable下创建一个search_background

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#F5F5F5" />
    <corners android:radius="24dp" />
    <stroke
        android:width="1dp"
        android:color="#E0E0E0" />
</shape> 

在Activity_main.xml文件

 <!-- 搜索框区域 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="8dp"
            android:layout_marginHorizontal="16dp"
            android:layout_marginBottom="8dp"
            android:background="@drawable/search_background"
            android:elevation="2dp">

            <!-- 搜索图标 -->
            <ImageView
                android:id="@+id/search_icon"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="8dp"
                android:src="@android:drawable/ic_search_category_default"
                app:tint="#666666" />

            <!-- 搜索输入框 -->
            <EditText
                android:id="@+id/search_input"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:layout_weight="1"
                android:background="@null"
                android:hint="搜索目的地"
                android:textColorHint="#999999"
                android:textColor="#333333"
                android:textSize="16sp"
                android:singleLine="true"
                android:imeOptions="actionSearch"
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp" />

        </LinearLayout>
④规划和导航功能完整的代码
  • 导航初始化 :
    MainActivity.java 中,通过 initNaviAndRoute 方法初始化了导航功能,使用了高德地图的AMapNavi SDK。

  • 路线规划 :
    通过 planRoute 方法进行路线规划,支持驾车导航。

  • 导航控制 :
    在底部面板中有一个"开始导航"按钮,点击后会调用 startNavi 方法启动真实导航。

  • 导航事件监听 :
    实现了 AMapNaviListener 接口,处理各种导航事件,如:

  • 导航初始化成功/失败

  • 开始导航

  • 到达目的地

  • 路线计算成功/失败

  • 位置变化等

  • 路线绘制 :
    通过 drawRouteLine 方法在地图上绘制导航路线。

  • 导航信息展示 :
    在底部面板中显示导航相关信息,包括目的地、距离和预计时间,通过 updateBottomSheetInfo 方法更新。

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.amap.api.maps.AMap;
import com.amap.api.maps.MapView;
import com.amap.api.maps.MapsInitializer;
import com.amap.api.maps.model.MyLocationStyle;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.navi.AMapNavi;
import com.amap.api.navi.AMapNaviListener;
import com.amap.api.navi.enums.PathPlanningStrategy;
import com.amap.api.navi.model.AMapCalcRouteResult;
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.AMapNaviPath;
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.NaviLatLng;
import com.amap.api.services.core.AMapException;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.core.PoiItem;
import com.amap.api.services.route.BusRouteResult;
import com.amap.api.services.route.DriveRouteResult;
import com.amap.api.services.route.RideRouteResult;
import com.amap.api.services.route.RouteSearch;
import com.amap.api.services.route.WalkRouteResult;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.amap.api.services.poisearch.PoiSearch;
import com.amap.api.services.poisearch.PoiResult;
import com.amap.api.maps.model.PolylineOptions;
import com.amap.api.maps.model.LatLngBounds;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements RouteSearch.OnRouteSearchListener, AMapNaviListener, PoiSearch.OnPoiSearchListener {
    // 地图视图组件
    private MapView mapView;
    // 地图控制器对象
    private AMap aMap;
    // 导航控制器对象
    private AMapNavi mAMapNavi;
    // 路线搜索对象
    private RouteSearch routeSearch;
    // 目的地标记
    private Marker destinationMarker;
    // 定位权限请求码
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
    // 需要进行检测的权限数组
    private static final String[] NEEDED_PERMISSIONS = {
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    };
    private BottomSheetBehavior<View> bottomSheetBehavior;
    private TextView tvDestination, tvDistance, tvTime;
    private Button btnStartNavi;
    private LatLonPoint destinationPoint;  // 保存目的地坐标

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        try {
            MapsInitializer.updatePrivacyShow(this, true, true);
            MapsInitializer.updatePrivacyAgree(this, true);
            
        setContentView(R.layout.activity_main);
            
            // 初始化底部面板
            initBottomSheet();
            
            mapView = findViewById(R.id.map);
            mapView.onCreate(savedInstanceState);
            
            if (aMap == null) {
                aMap = mapView.getMap();
                setupMapUI();
            }

            initNaviAndRoute();
            checkAndRequestPermissions();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "初始化失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 设置地图UI和定位样式
     */
    private void setupMapUI() {
        try {
            // 设置定位蓝点的样式
            MyLocationStyle myLocationStyle = new MyLocationStyle();
            // 连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动
            myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);
            // 设置定位蓝点的Style
            myLocationStyle.strokeColor(Color.argb(180, 3, 145, 255));// 设置圆形的边框颜色
            myLocationStyle.radiusFillColor(Color.argb(10, 0, 0, 180));// 设置圆形的填充颜色
            myLocationStyle.strokeWidth(5.0f);// 设置圆形的边框粗细
            // 设置定位蓝点的 Style
            aMap.setMyLocationStyle(myLocationStyle);

            // 设置定位监听
            aMap.setOnMyLocationChangeListener(location -> {
                // 当位置发生变化时,将地图中心点移动到当前位置
                aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                        new com.amap.api.maps.model.LatLng(location.getLatitude(), location.getLongitude()), 
                        17)); // 设置缩放级别为17
            });
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "设置地图UI失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 初始化导航和路线搜索
     */
    private void initNaviAndRoute() {
        try {
            // 初始化路线规划
            routeSearch = new RouteSearch(this);
            routeSearch.setRouteSearchListener(this);

            // 初始化导航
            mAMapNavi = AMapNavi.getInstance(getApplicationContext());
            mAMapNavi.addAMapNaviListener(this);

            // 设置地图点击监听,用于选择目的地
            aMap.setOnMapClickListener(latLng -> {
                try {
                    // 清除之前的标记
                    if (destinationMarker != null) {
                        destinationMarker.remove();
                    }
                    // 添加新的目的地标记
                    MarkerOptions markerOptions = new MarkerOptions()
                            .position(latLng)
                            .title("目的地");
                    destinationMarker = aMap.addMarker(markerOptions);

                    // 开始路线规划
                    planRoute(latLng);
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(this, "设置目的地失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "初始化导航失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 检查并请求定位权限
     */
    private void checkAndRequestPermissions() {
        if (lacksPermissions(NEEDED_PERMISSIONS)) {
            ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, LOCATION_PERMISSION_REQUEST_CODE);
        } else {
            // 已经有权限,启用定位图层
            enableMyLocation();
        }
    }

    /**
     * 检查是否缺少权限
     */
    private boolean lacksPermissions(String... permissions) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != 
                    PackageManager.PERMISSION_GRANTED) {
                return true;
            }
        }
        return false;
    }

    /**
     * 处理权限请求结果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0) {
                boolean allGranted = true;
                for (int grantResult : grantResults) {
                    if (grantResult != PackageManager.PERMISSION_GRANTED) {
                        allGranted = false;
                        break;
                    }
                }
                if (allGranted) {
                    // 获得权限,启用定位图层
                    enableMyLocation();
                } else {
                    Toast.makeText(this, "需要定位权限才能显示当前位置", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    /*启用定位图层*/
    private void enableMyLocation() {
        // 设置为true表示启动显示定位蓝点
        aMap.setMyLocationEnabled(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mapView.onResume();
    }
    @Override
    protected void onPause() {
        super.onPause();
        mapView.onPause();
    }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            if (mapView != null) {
                mapView.onDestroy();
            }
            if (mAMapNavi != null) {
                mAMapNavi.destroy();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onInitNaviFailure() {
        // 导航初始化失败
        Toast.makeText(this, "导航初始化失败", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onInitNaviSuccess() {
        // 导航初始化成功
        Toast.makeText(this, "导航初始化成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onStartNavi(int type) {
        // 开始导航回调
        Toast.makeText(this, "开始导航", Toast.LENGTH_SHORT).show();
    }

    @Override public void onTrafficStatusUpdate() {}

    @Override
    public void onLocationChange(AMapNaviLocation location) {
        // 导航过程中位置变化
        }

    @Override public void onGetNavigationText(int i, String s) {}
    @Override public void onGetNavigationText(String s) {}
    @Override public void onEndEmulatorNavi() {}

    @Override
    public void onArriveDestination() {
        // 到达目的地
        Toast.makeText(this, "到达目的地", Toast.LENGTH_SHORT).show();
    }

    @Override public void onCalculateRouteFailure(int i) {}
    @Override public void onReCalculateRouteForYaw() {}
    @Override public void onReCalculateRouteForTrafficJam() {}
    @Override
    public void onArrivedWayPoint(int i) {}
    @Override public void onGpsOpenStatus(boolean b) {}
    @Override public void onNaviInfoUpdate(NaviInfo naviInfo) {}
    @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) {}
    @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 result) {
        Toast.makeText(this, "路线规划成功", Toast.LENGTH_SHORT).show();
        AMapNaviPath path = mAMapNavi.getNaviPath();
        if (path != null) {
            // 更新底部面板信息
            updateBottomSheetInfo(path);
            // 绘制路线
            drawRouteLine(path);
        }
    }

    @Override
    public void onCalculateRouteFailure(AMapCalcRouteResult result) {
        // 路线规划失败
        Toast.makeText(this, "路线规划失败:" + result.getErrorDetail(), Toast.LENGTH_SHORT).show();
    }

     @Override public void onNaviRouteNotify(AMapNaviRouteNotifyData aMapNaviRouteNotifyData) {}
    @Override public void onGpsSignalWeak(boolean b) {}
    @Override public void onBusRouteSearched(BusRouteResult result, int errorCode) {}
    @Override public void onDriveRouteSearched(DriveRouteResult result, int errorCode) {}
    @Override public void onWalkRouteSearched(WalkRouteResult result, int errorCode) {}
    @Override public void onRideRouteSearched(RideRouteResult result, int errorCode) {}

    private void planRoute(LatLng destination) {
        try {
            if (aMap.getMyLocation() == null) {
                Toast.makeText(this, "等待定位信息...", Toast.LENGTH_SHORT).show();
                return;
            }

            // 获取起点(当前位置)
            LatLonPoint startPoint = new LatLonPoint(
                    aMap.getMyLocation().getLatitude(),
                    aMap.getMyLocation().getLongitude()
            );
            // 终点
            LatLonPoint endPoint = new LatLonPoint(destination.latitude, destination.longitude);

            // 构建导航数据
            List<NaviLatLng> startList = new ArrayList<>();
            List<NaviLatLng> endList = new ArrayList<>();
            startList.add(new NaviLatLng(startPoint.getLatitude(), startPoint.getLongitude()));
            endList.add(new NaviLatLng(endPoint.getLatitude(), endPoint.getLongitude()));

            // 开始算路
            mAMapNavi.calculateDriveRoute(startList, endList, null, 2);
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "路线规划失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    private void initBottomSheet() {
        try {
            View bottomSheet = findViewById(R.id.bottom_sheet);
            bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
            
            // 设置初始状态为折叠
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            
            // 初始化视图
            tvDestination = findViewById(R.id.tv_destination);
            tvDistance = findViewById(R.id.tv_distance);
            tvTime = findViewById(R.id.tv_time);
            btnStartNavi = findViewById(R.id.btn_start_navi);

            // 初始化搜索框
            EditText searchInput = findViewById(R.id.search_input);
            searchInput.setOnEditorActionListener((v, actionId, event) -> {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    String keyword = searchInput.getText().toString().trim();
                    if (!TextUtils.isEmpty(keyword)) {
                        // 隐藏键盘
                        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                        imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0);
                        
                        // 开始搜索
                        searchLocation(keyword);
                        return true;
                    }
                }
                return false;
            });

            // 设置底部面板的状态改变监听
            bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                @Override
                public void onStateChanged(@NonNull View bottomSheet, int newState) {
                    switch (newState) {
                        case BottomSheetBehavior.STATE_COLLAPSED:
                            // 面板折叠状态
                            break;
                        case BottomSheetBehavior.STATE_EXPANDED:
                            // 面板展开状态
                            break;
                    }
                }

                @Override
                public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                    // 滑动时的处理
                }
            });

            // 设置开始导航按钮点击事件
            btnStartNavi.setOnClickListener(v -> {
                if (mAMapNavi != null) {
                    mAMapNavi.startNavi(1); // 1表示真实导航
                    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "初始化底部面板失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    /**搜索位置*/
    private void searchLocation(String keyword) {
        try {
            Toast.makeText(this, "正在搜索: " + keyword, Toast.LENGTH_SHORT).show();
            
            // 创建POI搜索对象
            PoiSearch.Query query = new PoiSearch.Query(keyword, "", "全国");  // 设置搜索范围为全国
            query.setPageSize(10); // 设置每页最多返回多少条数据
            query.setPageNum(1);   // 设置查询第几页,从1开始

            PoiSearch poiSearch = new PoiSearch(this, query);
            poiSearch.setOnPoiSearchListener(this);
            
            // 如果当前位置可用,设置搜索中心点
            if (aMap != null && aMap.getMyLocation() != null) {
                poiSearch.setBound(new PoiSearch.SearchBound(
                    new LatLonPoint(aMap.getMyLocation().getLatitude(), 
                                  aMap.getMyLocation().getLongitude()), 
                    50000));  // 设置搜索半径为50公里
            }
            
            poiSearch.searchPOIAsyn(); // 开始异步搜索
        } catch (AMapException e) {
            e.printStackTrace();
            Toast.makeText(this, "搜索初始化失败:" + e.getErrorMessage(), Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "搜索失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onPoiSearched(PoiResult result, int rCode) {
        if (rCode == AMapException.CODE_AMAP_SUCCESS) {
            if (result != null && result.getPois() != null && !result.getPois().isEmpty()) {
                // 获取第一个POI点
                PoiItem poi = result.getPois().get(0);
                // 更新目的地信息
                updateDestinationInfo(poi);
                // 移动地图到搜索位置
                moveMapToLocation(poi.getLatLonPoint());
                // 规划路线
                planRouteToDestination(poi.getLatLonPoint());
            } else {
                Toast.makeText(this, "未找到相关地点,请尝试其他关键词", Toast.LENGTH_SHORT).show();
            }
        } else {
            String errorMessage;
            switch (rCode) {
                case AMapException.CODE_AMAP_ENGINE_RESPONSE_ERROR:
                    errorMessage = "服务响应错误";
                    break;
                case AMapException.CODE_AMAP_ENGINE_CONNECT_TIMEOUT:
                    errorMessage = "连接超时";
                    break;
                case AMapException.CODE_AMAP_ENGINE_RETURN_TIMEOUT:
                    errorMessage = "请求超时";
                    break;
                default:
                    errorMessage = "未知错误(错误码:" + rCode + ")";
            }
            Toast.makeText(this, "搜索失败:" + errorMessage, Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onPoiItemSearched(PoiItem poiItem, int rCode) {
        // 处理单个POI搜索结果
    }

    private void updateDestinationInfo(PoiItem poi) {
        tvDestination.setText("目的地:" + poi.getTitle());
        // 保存目的地信息,用于后续导航
        destinationPoint = poi.getLatLonPoint();
        // 展开面板
        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    }

    private void moveMapToLocation(LatLonPoint point) {
        if (aMap != null) {
            // 清除之前的标记
            aMap.clear();
            // 添加新的标记
            LatLng latLng = new LatLng(point.getLatitude(), point.getLongitude());
            aMap.addMarker(new MarkerOptions()
                    .position(latLng)
                    .title("目的地"));
            // 移动地图
            aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));
        }
    }

    private void planRouteToDestination(LatLonPoint destination) {
        if (aMap.getMyLocation() == null) {
            Toast.makeText(this, "无法获取当前位置", Toast.LENGTH_SHORT).show();
            return;
        }

        // 构建导航起终点
        List<NaviLatLng> startList = new ArrayList<>();
        List<NaviLatLng> endList = new ArrayList<>();

        // 起点为当前位置
        startList.add(new NaviLatLng(aMap.getMyLocation().getLatitude(),
                aMap.getMyLocation().getLongitude()));
        // 终点为搜索的位置
        endList.add(new NaviLatLng(destination.getLatitude(),
                destination.getLongitude()));

        // 开始算路
        mAMapNavi.calculateDriveRoute(startList, endList, null, 
                PathPlanningStrategy.DRIVING_DEFAULT);
    }

    private void drawRouteLine(AMapNaviPath path) {
        if (aMap != null) {
            // 清除之前的路线
            aMap.clear();
            // 绘制新路线
            AMapNaviPath naviPath = mAMapNavi.getNaviPath();
            if (naviPath != null) {
                // 获取路线坐标点
                List<NaviLatLng> coordinates = naviPath.getCoordList();
                if (coordinates != null && coordinates.size() > 0) {
                    List<LatLng> latLngs = new ArrayList<>();
                    for (NaviLatLng coordinate : coordinates) {
                        latLngs.add(new LatLng(coordinate.getLatitude(), coordinate.getLongitude()));
                    }
                    // 绘制路线
                    aMap.addPolyline(new PolylineOptions()
                            .addAll(latLngs)
                            .width(20)
                            .color(Color.BLUE));
                    
                    // 添加起点和终点标记
                    LatLng startPoint = latLngs.get(0);
                    LatLng endPoint = latLngs.get(latLngs.size() - 1);
                    
                    // 添加起点标记
                    aMap.addMarker(new MarkerOptions()
                            .position(startPoint)
                            .title("起点"));
                    
                    // 添加终点标记
                    aMap.addMarker(new MarkerOptions()
                            .position(endPoint)
                            .title("终点"));

                    // 调整地图视野以显示整条路线
                    LatLngBounds bounds = new LatLngBounds.Builder()
                            .include(startPoint)
                            .include(endPoint)
                            .build();
                    aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100));
                }
            }
        }
    }

    private void updateBottomSheetInfo(AMapNaviPath path) {
        if (path != null) {
            // 更新距离信息(转换为公里)
            String distance = String.format("距离:%.1f公里", path.getAllLength() / 1000.0);
            tvDistance.setText(distance);

            // 更新时间信息(转换为分钟)
            String time = String.format("预计用时:%d分钟", path.getAllTime() / 60);
            tvTime.setText(time);

            // 如果有目的地标记,更新目的地信息
            if (destinationMarker != null) {
                String destination = "目的地:" + destinationMarker.getTitle();
                tvDestination.setText(destination);
            }
        }
    }
}

XML文件

<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" />

        <!-- 搜索框区域 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="8dp"
            android:layout_marginHorizontal="16dp"
            android:layout_marginBottom="8dp"
            android:background="@drawable/search_background"
            android:elevation="2dp">

            <!-- 搜索图标 -->
            <ImageView
                android:id="@+id/search_icon"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_gravity="center_vertical"
                android:layout_marginStart="8dp"
                android:src="@android:drawable/ic_search_category_default"
                app:tint="#666666" />

            <!-- 搜索输入框 -->
            <EditText
                android:id="@+id/search_input"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:layout_weight="1"
                android:background="@null"
                android:hint="搜索目的地"
                android:textColorHint="#999999"
                android:textColor="#333333"
                android:textSize="16sp"
                android:singleLine="true"
                android:imeOptions="actionSearch"
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp" />

        </LinearLayout>

        <!-- 导航信息区域 -->
        <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 方法(核心偏航处理方法)
@Override
public void onReCalculateRouteForYaw() {
    // 偏航重新计算路线回调
    requireActivity().runOnUiThread(() -> {
        String message = "您已偏离规划路线,正在重新规划";
        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
        if (!isMuted && textToSpeech != null) {
            textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
        }
    });
}
②、calculateRoute 方法(路线重新规划)
private void calculateRoute() {
    if (mStartPoint == null) {
        // 如果没有起点,使用当前位置
        Location location = aMap.getMyLocation();
        if (location != null) {
            mStartPoint = new LatLng(location.getLatitude(), location.getLongitude());
        } else {
            Toast.makeText(requireContext(), "无法获取当前位置", Toast.LENGTH_SHORT).show();
            return;
        }
    }

    try {
        // 清除之前的路线
        aMap.clear();

        // 准备导航起终点
        List<NaviLatLng> startPoints = new ArrayList<>();
        startPoints.add(new NaviLatLng(mStartPoint.latitude, mStartPoint.longitude));
        List<NaviLatLng> endPoints = new ArrayList<>();
        endPoints.add(new NaviLatLng(mEndPoint.latitude, mEndPoint.longitude));

        // 根据出行方式计算路线
        switch (currentNaviMode) {
            case 0: // 驾车
                mAMapNavi.calculateDriveRoute(startPoints, endPoints, null, 
                    PathPlanningStrategy.DRIVING_DEFAULT);
                break;
  
        }
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(requireContext(), "路线规划失败:" + e.getMessage(), 
            Toast.LENGTH_SHORT).show();
    }
}
③、onCalculateRouteSuccess 方法(路线重新规划成功回调)
@Override
public void onCalculateRouteSuccess(AMapCalcRouteResult result) {
    if (result != null) {
        try {
            // 清除之前的标记和路线
            aMap.clear();
            mapPolylines.clear();

            // 添加起点和终点标记
            if (mStartPoint != null) {
                MarkerOptions startMarker = new MarkerOptions()
                    .position(mStartPoint)
                    .title("起点")
                    .snippet("当前位置")
                    .icon(BitmapDescriptorFactory.defaultMarker(
                        BitmapDescriptorFactory.HUE_GREEN));
                aMap.addMarker(startMarker);
            }

            if (mEndPoint != null) {
                MarkerOptions endMarker = new MarkerOptions()
                    .position(mEndPoint)
                    .title("终点")
                    .snippet("目的地")
                    .icon(BitmapDescriptorFactory.defaultMarker(
                        BitmapDescriptorFactory.HUE_RED));
                aMap.addMarker(endMarker);
            }

            // 获取所有路线ID并绘制路线
            int[] routeIds = result.getRouteid();
            if (routeIds != null && routeIds.length > 0) {
                currentRouteIds = routeIds;
                updateRouteOptions();
                drawSelectedRoute();
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(requireContext(), "路线显示失败:" + e.getMessage(), 
                Toast.LENGTH_SHORT).show();
        }
    }
}
④、drawSelectedRoute 方法(绘制新路线)
private void drawSelectedRoute() {
    try {
        // 选择路线
        mAMapNavi.selectRouteId(currentRouteIds[selectedRouteIndex]);

        // 清除之前的路线
        for (Polyline line : mapPolylines) {
            line.remove();
        }
        mapPolylines.clear();

        // 获取路线信息
        List<NaviLatLng> pathPoints = mAMapNavi.getNaviPath().getCoordList();

        if (pathPoints != null && !pathPoints.isEmpty()) {
            // 设置路线颜色
            int pathColor;
            switch (currentNaviMode) {
                case 1: // 步行
                    pathColor = Color.GREEN;
                    break;
            }

            // 转换坐标点并绘制路线
            List<LatLng> mapPoints = new ArrayList<>();
            for (NaviLatLng point : pathPoints) {
                mapPoints.add(new LatLng(point.getLatitude(), point.getLongitude()));
            }

            // 添加新路线
            Polyline polyline = aMap.addPolyline(new PolylineOptions()
                .addAll(mapPoints)
                .width(20)
                .color(pathColor)
                .zIndex(1)
                .setDottedLine(false));
            mapPolylines.add(polyline);

            // 调整地图视野以显示整条路线
            LatLngBounds.Builder builder = new LatLngBounds.Builder();
            for (LatLng point : mapPoints) {
                builder.include(point);
            }
            aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(
                builder.build(), 100));
        }
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(requireContext(), "路线绘制失败:" + e.getMessage(), 
            Toast.LENGTH_SHORT).show();
    }
}

五、其他功能

1、语音播报功能
① TextToSpeech 基本介绍与用法

​ TextToSpeech 是 Android 提供的文字转语音引擎,可以将文本转换为语音输出。

1)初始化方式
private TextToSpeech textToSpeech;

// 方式一:基础初始化
textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
    @Override
    public void onInit(int status) {
        if (status == TextToSpeech.SUCCESS) {
            // 设置语言
            int result = textToSpeech.setLanguage(Locale.CHINESE);
            
            // 检查语言是否支持
            if (result == TextToSpeech.LANG_MISSING_DATA || 
                result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Toast.makeText(context, "语言不支持", Toast.LENGTH_SHORT).show();
            }
        } else {
            Toast.makeText(context, "初始化失败", Toast.LENGTH_SHORT).show();
        }
    }
});

// 方式二:Lambda表达式(简化写法)
textToSpeech = new TextToSpeech(requireContext(), status -> {
    if (status == TextToSpeech.SUCCESS) {
        textToSpeech.setLanguage(Locale.CHINESE);
    }
});
2)主要配置方法
// 设置语言
textToSpeech.setLanguage(Locale.CHINESE);

// 设置语速(1.0为正常速度)
textToSpeech.setSpeechRate(1.0f);

// 设置音调(1.0为正常音调)
textToSpeech.setPitch(1.0f);

// 设置音量(0.0-1.0)
textToSpeech.setVolume(1.0f);

// 设置音频流类型
textToSpeech.setAudioAttributes(new AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
    .build());
3)语音播报方法
// 1. 基本播报方法
textToSpeech.speak(
    "要播报的文本",           // 文本内容
    TextToSpeech.QUEUE_FLUSH, // 播报模式
    null,                    // Bundle参数
    "utteranceId"           // 话语ID
);

// 2. 立即播报(清除队列)
textToSpeech.speak(
    "紧急提示信息", 
    TextToSpeech.QUEUE_FLUSH, 
    null, 
    null
);

// 3. 加入播报队列
textToSpeech.speak(
    "普通提示信息", 
    TextToSpeech.QUEUE_ADD, 
    null, 
    null
);

// 4. 带参数的播报
Bundle params = new Bundle();
params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, 0.8f);
params.putFloat(TextToSpeech.Engine.KEY_PARAM_PITCH, 1.2f);
textToSpeech.speak(
    "自定义参数播报", 
    TextToSpeech.QUEUE_FLUSH, 
    params, 
    "customId"
);
4)播报状态监听
textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
    @Override
    public void onStart(String utteranceId) {
        // 开始播报
    }

    @Override
    public void onDone(String utteranceId) {
        // 播报完成
    }

    @Override
    public void onError(String utteranceId) {
        // 播报错误
    }
});
5)权限要求
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
②使用示例(步骤解析)
1). 初始化语音系统
// 在类成员变量中声明
private TextToSpeech textToSpeech;

// 在 onCreateView 中初始化
textToSpeech = new TextToSpeech(requireContext(), status -> {
    if (status == TextToSpeech.SUCCESS) {
        // 设置语音为中文
        textToSpeech.setLanguage(Locale.CHINESE);
    }
});
2).导航语音播报实现
//开始导航
private void startNavi() {
    if (mStartPoint == null || mEndPoint == null) {
        Toast.makeText(requireContext(), "请先规划路线", Toast.LENGTH_SHORT).show();
        return;
    }

    try {
        mAMapNavi.selectRouteId(currentRouteIds[selectedRouteIndex]);
        mAMapNavi.startNavi(NaviType.GPS);
        isNavigating = true;
        updateNavigationButtons(true);
        showNavigationInfoPanel();

        // 这里可以添加开始导航的语音提示
        if (!isMuted && textToSpeech != null) {
            textToSpeech.speak("开始导航", TextToSpeech.QUEUE_FLUSH, null, null);
        }
        
        // ... 其他代码
    } catch (Exception e) {
        e.printStackTrace();
    }
}


//导航过程中的语音提示:
@Override
public void onGetNavigationText(String text) {
    try {
        // 这是导航过程中的语音播报核心方法
        if (!isMuted && textToSpeech != null && text != null) {
            textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, null);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
@Override
public void onGetNavigationText(int type, String text) {
    onGetNavigationText(text);
}


// 到达目的地提醒
@Override
public void onArriveDestination() {
    String message = "已到达目的地,本次导航结束";
    Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
    // 播放语音提醒
    if (!isMuted && textToSpeech != null) {
        textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
    }
    // 震动提醒
    if (ContextCompat.checkSelfPermission(requireContext(), 
        android.Manifest.permission.VIBRATE) == PackageManager.PERMISSION_GRANTED) {
        android.os.Vibrator vibrator = (android.os.Vibrator) 
            requireContext().getSystemService(Context.VIBRATOR_SERVICE);
        if (vibrator != null && vibrator.hasVibrator()) {
            vibrator.vibrate(500); // 震动500毫秒
        }
    }
}

3). 特殊情况的语音提醒(按需选择)

// 偏航重新规划提醒
@Override
public void onReCalculateRouteForYaw() {
    requireActivity().runOnUiThread(() -> {
        String message = "您已偏离规划路线,正在重新规划";
        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
        if (!isMuted && textToSpeech != null) {
            textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
        }
    });
}

// 拥堵重新规划提醒
@Override
public void onReCalculateRouteForTrafficJam() {
    requireActivity().runOnUiThread(() -> {
        String message = "前方道路拥堵,正在重新规划路线";
        Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
        if (!isMuted && textToSpeech != null) {
            textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
        }
    });
}
4). 交通设施语音提醒
@Override
public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo facility) {
    if (facility == null || !isTrafficEnabled) return;

    String message = "";
    switch (facility.getBroadcastType()) {
        case 0:  // 测速摄像头
            message = String.format("前方%d米有测速摄像头,限速%d公里/小时",
                facility.getDistance(), facility.getLimitSpeed());
            break;
        case 1:  // 监控摄像头
            message = String.format("前方%d米有监控摄像头", 
                facility.getDistance());
            break;
        case 2:  // 事故
            message = String.format("前方%d米发生交通事故,请谨慎驾驶", 
                facility.getDistance());
            break;
        case 3:  // 施工
            message = String.format("前方%d米正在施工,请减速慢行", 
                facility.getDistance());
            break;
        case 4:  // 交通拥堵
            message = String.format("前方%d米出现拥堵,建议绕行", 
                facility.getDistance());
            break;
    }

    if (!TextUtils.isEmpty(message)) {
        final String finalMessage = message;
        requireActivity().runOnUiThread(() -> {
            // 显示提示信息
            Toast.makeText(requireContext(), finalMessage, Toast.LENGTH_LONG).show();

            // 播放语音提醒
            if (!isMuted && textToSpeech != null) {
                textToSpeech.speak(finalMessage, TextToSpeech.QUEUE_ADD, null, null);
            }
        });
    }
}
5). 速度超限提醒
private void showSpeedWarning(int currentSpeed) {
    String speedWarning = String.format("当前速度%d公里/小时,请注意行车安全",
        currentSpeed);
    Toast.makeText(requireContext(), speedWarning, Toast.LENGTH_SHORT).show();
    if (!isMuted && textToSpeech != null) {
        textToSpeech.speak("您的车速较快,请注意行车安全", 
            TextToSpeech.QUEUE_ADD, null, null);
    }
}
6). 资源释放
@Override
public void onDestroy() {
    super.onDestroy();
    if (textToSpeech != null) {
        textToSpeech.stop();    // 停止播报
        textToSpeech.shutdown(); // 关闭引擎
    }
}
2、实时路况监控
①路况开关控制
//MainActivity
 private ImageButton btnTraffic;
    private boolean isTrafficEnabled = true;  // 默认开启实时路况

 // 初始化实时路况按钮
        btnTraffic = rootView.findViewById(R.id.btn_traffic);


// 实时路况按钮点击事件
btnTraffic.setOnClickListener(v -> {
    isTrafficEnabled = !isTrafficEnabled;
    aMap.setTrafficEnabled(isTrafficEnabled);  // 设置地图是否显示实时路况
    updateTrafficButton();

    // 显示提示
    String message = isTrafficEnabled ? "已开启实时路况" : "已关闭实时路况";
    Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
});

// 更新路况按钮状态
private void updateTrafficButton() {
    btnTraffic.setSelected(isTrafficEnabled);
    btnTraffic.setAlpha(isTrafficEnabled ? 1.0f : 0.5f);
}

XML文件

<!-- 实时路况按钮 -->
<ImageButton
    android:id="@+id/btn_traffic"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_margin="8dp"
    android:background="@drawable/bg_map_button"
    android:contentDescription="@string/traffic_button"
    android:src="@drawable/ic_traffic" />

按钮样式: res/drawable/bg_map_button.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="oval">
            <solid android:color="#E0E0E0" />
        </shape>
    </item>
    <item android:state_selected="true">
        <shape android:shape="oval">
            <solid android:color="#E3F2FD" />
        </shape>
    </item>
    <item>
        <shape android:shape="oval">
            <solid android:color="#FFFFFF" />
        </shape>
    </item>
</selector>

路况图标样式:res/drawable/ic_traffic.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true"
        android:drawable="@drawable/ic_traffic_on" />
    <item
        android:drawable="@drawable/ic_traffic_off" />
</selector>

字符串资源:在values/strings

<string name="traffic_button">实时路况</string>
②路况状态更新
@Override
public void onTrafficStatusUpdate() {
    // 路况更新时的处理
    if (isTrafficEnabled) {
        requireActivity().runOnUiThread(() -> {
            // 可以在这里更新UI
        });
    }
}

@Override
public void onNaviInfoUpdate(NaviInfo naviInfo) {
    if (isNavigating && navigationInfoPanel.getVisibility() == View.VISIBLE && naviInfo != null) {
        requireActivity().runOnUiThread(() -> {
            // 更新路况状态(根据当前速度判断)
            int currentSpeed = naviInfo.getCurrentSpeed();
            String trafficText;
            int trafficColor;

            // 根据当前速度判断路况状态
            if (currentSpeed < 15) { // 低于15km/h认为是拥堵
                trafficText = "拥堵";
                trafficColor = Color.parseColor("#F44336"); // 红色
            } else if (currentSpeed < 30) { // 低于30km/h认为是缓行
                trafficText = "缓行";
                trafficColor = Color.parseColor("#FF9800"); // 橙色
            } else { // 高于30km/h认为是畅通
                trafficText = "畅通";
                trafficColor = Color.parseColor("#4CAF50"); // 绿色
            }

            tvTrafficStatus.setText("当前路况:" + trafficText);
            tvTrafficStatus.setTextColor(trafficColor);
        });
    }
}
③拥堵重新规划
@Override
public void onReCalculateRouteForTrafficJam() {
    requireActivity().runOnUiThread(() -> {
        String message = "前方道路拥堵,正在重新规划路线";
        Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show();
        if (!isMuted && textToSpeech != null) {
            textToSpeech.speak(message, TextToSpeech.QUEUE_FLUSH, null, null);
        }
    });
}
3、安全提醒系统
①OnUpdateTrafficFacility 方法(交通设施安全提醒)
@Override
public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo[] facilities) {
    // 批量交通设施提醒
    if (facilities == null || facilities.length == 0 || !isTrafficEnabled) return;

    for (AMapNaviTrafficFacilityInfo facility : facilities) {
        String message = "";
        switch (facility.getBroadcastType()) {
            case 0:  // 测速摄像头
                message = String.format("前方%d米有测速摄像头,限速%d公里/小时",
                    facility.getDistance(), facility.getLimitSpeed());
                break;
            case 1:  // 监控摄像头
                message = String.format("前方%d米有监控摄像头", facility.getDistance());
                break;
            case 2:  // 事故
                message = String.format("前方%d米发生交通事故,请谨慎驾驶", facility.getDistance());
                break;
            case 3:  // 施工
                message = String.format("前方%d米正在施工,请减速慢行", facility.getDistance());
                break;
            case 4:  // 交通拥堵
                message = String.format("前方%d米出现拥堵,建议绕行", facility.getDistance());
                break;
        }

        // 发送提醒
        if (!TextUtils.isEmpty(message)) {
            final String finalMessage = message;
            requireActivity().runOnUiThread(() -> {
                // 显示提示信息
                Toast.makeText(requireContext(), finalMessage, Toast.LENGTH_LONG).show();
                // 语音播报
                if (!isMuted && textToSpeech != null) {
                    textToSpeech.speak(finalMessage, TextToSpeech.QUEUE_ADD, null, null);
                }
            });
        }
    }
}
②onNaviInfoUpdate 方法中的速度监控
@Override
public void onNaviInfoUpdate(NaviInfo naviInfo) {
    if (isNavigating && navigationInfoPanel.getVisibility() == View.VISIBLE && naviInfo != null) {
        requireActivity().runOnUiThread(() -> {
            // ... 其他代码 ...

            // 速度监控
            int currentSpeed = naviInfo.getCurrentSpeed();
            if (currentSpeed > 80) {
                String speedWarning = String.format("当前速度%d公里/小时,请注意行车安全",
                    currentSpeed);
                Toast.makeText(requireContext(), speedWarning, Toast.LENGTH_SHORT).show();
                if (!isMuted && textToSpeech != null) {
                    textToSpeech.speak("您的车速较快,请注意行车安全", 
                        TextToSpeech.QUEUE_ADD, null, null);
                }
            }
        });
    }
}
③getWarningMessage 方法(天气安全提醒)
private String getWarningMessage(String weatherType) {
    if (weatherType.contains("雨")) {
        return "注意:当前降雨可能影响出行安全,请携带雨具,谨慎驾驶。";
    } else if (weatherType.contains("雪")) {
        return "注意:当前降雪可能导致路面结冰,建议减少出行。";
    } else if (weatherType.contains("雾")) {
        return "注意:空气质量较差,建议戴口罩出行。";
    } else if (weatherType.contains("霾")) {
        return "注意:空气质量较差,建议戴口罩出行。";
    }
    return "";
}
4、天气信息和预警
①获取高德天气服务KEY

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

在这里插入图片描述

在这里插入图片描述

将获取的KEY复制到AndroidManifest里

在这里插入图片描述

②调用天气服务的方法
1)updateWeatherInfo 方法(核心方法)
private void updateWeatherInfo(LatLng destination) {
    try {
        // 清除旧的天气信息
        clearWeatherInfo();

        // 计算并显示距离
        float distance = calculateDistance(null, destination);
        if (distance > 0) {
            final float finalDistance = distance;
            requireActivity().runOnUiThread(() -> {
                tvDistance.setText(String.format("距离: %.1fkm", finalDistance));
            });
        }

        // 使用逆地理编码获取城市名称
        GeocodeSearch geocodeSearch = new GeocodeSearch(requireContext());
        geocodeSearch.setOnGeocodeSearchListener(new GeocodeSearch.OnGeocodeSearchListener() {
            @Override
            public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
                if (rCode == AMapException.CODE_AMAP_SUCCESS) {
                    // 获取城市名称
                    String city = result.getRegeocodeAddress().getCity();
                    if (TextUtils.isEmpty(city)) {
                        city = result.getRegeocodeAddress().getDistrict();
                    }

                    // 使用城市名称查询天气
                    WeatherSearchQuery query = new WeatherSearchQuery(
                        city,
                        WeatherSearchQuery.WEATHER_TYPE_LIVE
                    );
                    WeatherSearch weatherSearch = new WeatherSearch(requireContext());
                    weatherSearch.setOnWeatherSearchListener(NavigationFragment.this);
                    weatherSearch.setQuery(query);
                    weatherSearch.searchWeatherAsyn();
                }
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}
2)onWeatherLiveSearched 方法(天气查询回调)
@Override
public void onWeatherLiveSearched(LocalWeatherLiveResult weatherLiveResult, int rCode) {
    requireActivity().runOnUiThread(() -> {
        if (rCode == AMapException.CODE_AMAP_SUCCESS) {
            LocalWeatherLive weatherLive = weatherLiveResult.getLiveResult();
            
            // 更新天气信息UI
            tvWeather.setText(String.format("%s %s°C", 
                weatherLive.getWeather(), 
                weatherLive.getTemperature()));
            tvHumidity.setText(weatherLive.getHumidity() + "%");
            tvWind.setText(String.format("%s风 %s级", 
                weatherLive.getWindDirection(), 
                weatherLive.getWindPower()));

            // 更新空气质量
            String airQuality = getAirQualityByWeather(weatherLive.getWeather());
            tvAqi.setText("空气质量:" + airQuality);
            tvAqi.setTextColor(getAirQualityColor(airQuality));

            // 更新天气图标
            updateWeatherIcon(weatherLive.getWeather());

            // 生成出行建议
            String recommendation = generateRecommendation(
                weatherLive.getWeather(), 
                calculateDistance(mStartPoint, mEndPoint)
            );
            tvRecommendation.setText(recommendation);

            // 显示天气警告
            if (shouldShowWarning(weatherLive.getWeather())) {
                tvWeatherWarning.setVisibility(View.VISIBLE);
                tvWeatherWarning.setText(getWarningMessage(weatherLive.getWeather()));
            }
        }
    });
}
3)getAirQualityByWeather 方法(获取空气质量)
private String getAirQualityByWeather(String weather) {
    if (weather.contains("霾") || weather.contains("沙尘")) {
        return "重度污染";
    } else if (weather.contains("雾")) {
        return "中度污染";
    } else if (weather.contains("阴") || weather.contains("多云")) {
        return "良";
    } else if (weather.contains("雨") || weather.contains("雪")) {
        return "良";
    } else if (weather.contains("晴")) {
        return "优";
    } else {
        return "良";
    }
}

//获取空气质量对应颜色
private int getAirQualityColor(String quality) {
    switch (quality) {
        case "优":
            return Color.parseColor("#00C853");
        case "良":
            return Color.parseColor("#FFB300");
        case "轻度污染":
            return Color.parseColor("#FB8C00");
        case "中度污染":
            return Color.parseColor("#F4511E");
        case "重度污染":
            return Color.parseColor("#C62828");
        case "严重污染":
            return Color.parseColor("#6A1B9A");
        default:
            return Color.GRAY;
    }
}
4)clearWeatherInfo 方法(清除天气信息)
private void clearWeatherInfo() {
    requireActivity().runOnUiThread(() -> {
        if (tvWeather != null) tvWeather.setText("正在获取天气...");
        if (tvHumidity != null) tvHumidity.setText("");
        if (tvWind != null) tvWind.setText("");
        if (tvDistance != null) tvDistance.setText("");
        if (tvRecommendation != null) tvRecommendation.setText("");
        if (tvWeatherWarning != null) {
            tvWeatherWarning.setText("");
            tvWeatherWarning.setVisibility(View.GONE);
        }
    });
}

上述只是调用天气服务的方法,你还要创建一个显示天气信息的布局weather_info,例如:

    <!-- 天气信息卡片 -->
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        app:cardCornerRadius="12dp"
        app:cardElevation="2dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:gravity="center_vertical">

                <ImageView
                    android:id="@+id/weather_icon"
                    android:layout_width="48dp"
                    android:layout_height="48dp"
                    android:src="@drawable/ic_weather_sunny"/>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginStart="16dp"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/tv_destination"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textSize="16sp"
                        android:textColor="#333333"
                        android:textStyle="bold"
                        android:text="目的地"/>

                    <TextView
                        android:id="@+id/tv_weather"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="4dp"
                        android:textSize="14sp"
                        android:textColor="#666666"
                        android:text="晴天 26°C"/>
                </LinearLayout>

                <TextView
                    android:id="@+id/tv_distance"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="14sp"
                    android:textColor="#2196F3"
                    android:text="距离: 5.2km"/>
            </LinearLayout>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginTop="12dp"
                android:layout_marginBottom="12dp"
                android:background="#E0E0E0"/>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical"
                    android:gravity="center">

                    <TextView
                        android:id="@+id/tv_humidity"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textSize="14sp"
                        android:textColor="#333333"
                        android:text="湿度"/>

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="4dp"
                        android:textSize="12sp"
                        android:textColor="#666666"
                        android:text="65%"/>
                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical"
                    android:gravity="center">

                    <TextView
                        android:id="@+id/tv_wind"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textSize="14sp"
                        android:textColor="#333333"
                        android:text="风速"/>

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="4dp"
                        android:textSize="12sp"
                        android:textColor="#666666"
                        android:text="3级"/>
                </LinearLayout>

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:orientation="vertical"
                    android:gravity="center">

                    <TextView
                        android:id="@+id/tv_aqi"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textSize="14sp"
                        android:textColor="#333333"
                        android:text="空气质量:优"/>

                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </androidx.cardview.widget.CardView>

对控件进行初始化:

// 在 onCreateView 方法中初始化天气信息相关的控件
View weatherInfo = rootView.findViewById(R.id.weather_info);
weatherIcon = weatherInfo.findViewById(R.id.weather_icon);
tvDestination = weatherInfo.findViewById(R.id.tv_destination);
tvWeather = weatherInfo.findViewById(R.id.tv_weather);
tvDistance = weatherInfo.findViewById(R.id.tv_distance);
tvHumidity = weatherInfo.findViewById(R.id.tv_humidity);
tvWind = weatherInfo.findViewById(R.id.tv_wind);
tvAqi = weatherInfo.findViewById(R.id.tv_aqi);

天气信息面板的显示控制

 View weatherInfo = rootView.findViewById(R.id.weather_info);
    weatherInfo.setVisibility(View.VISIBLE);  // 显示天气
   //  weatherInfo.setVisibility(View.GONE);  // 隐藏天气

5、智能出行建议
①、generateRecommendation 方法(核心方法)
private String generateRecommendation(String weatherType, float distance) {
    // 根据天气类型给出建议
    if (weatherType.contains("暴雨") || weatherType.contains("大雨") || weatherType.contains("雷")) {
        return "当前天气恶劣,建议选择驾车出行或推迟行程,注意道路积水和打滑。";
    } else if (weatherType.contains("中雨") || weatherType.contains("小雨")) {
        return "当前有降雨,建议携带雨具,选择驾车出行较为安全。";
    } else if (weatherType.contains("雪")) {
        return "当前降雪天气,路面可能结冰,建议驾车出行并保持低速行驶。";
    } else if (weatherType.contains("雾") || weatherType.contains("霾")) {
        return "当前能见度较低,建议戴好防护口罩,驾车出行时开启雾灯并保持安全车距。";
    } 
    
    // 根据天气和距离组合给出建议
    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 "天气晴好,距离不远,建议步行,享受阳光。";
        }
    } else if (weatherType.contains("多云")) {
        if (distance > 5) {
            return "天气较好,但距离较远,建议驾车出行。";
        } else if (distance > 2) {
            return "云朵遮阳,温度舒适,是骑行的好天气。";
        } else {
            return "天气舒适,距离不远,非常适合步行。";
        }
    }
    
    // 默认建议
    if (distance > 5) {
        return "距离较远,建议选择驾车出行。";
    } else if (distance > 2) {
        return "距离适中,可以考虑骑行方式。";
    } else {
        return "距离较近,建议步行前往。";
    }
}
②、calculateDistance 方法(计算距离)
private float calculateDistance(LatLng start, LatLng end) {
    try {
        if (end == null) {
            return 0;
        }

        // 获取当前位置作为起点
        Location currentLocation = aMap.getMyLocation();
        if (currentLocation == null) {
            if (start == null) {
                return 0;
            }
        } else {
            // 使用当前实际位置作为起点
            start = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
        }

        // 计算距离
        if (start != null && end != null) {
            float distance = AMapUtils.calculateLineDistance(start, end);
            return distance / 1000.0f; // 转换为公里
        }
        return 0;
    } catch (Exception e) {
        e.printStackTrace();
        return 0;
    }
}
③、onWeatherLiveSearched 方法中的智能建议部分
@Override
public void onWeatherLiveSearched(LocalWeatherLiveResult weatherLiveResult, int rCode) {
    requireActivity().runOnUiThread(() -> {
        if (rCode == AMapException.CODE_AMAP_SUCCESS) {
            LocalWeatherLive weatherLive = weatherLiveResult.getLiveResult();
            
            // 生成出行建议
            String recommendation = generateRecommendation(
                weatherLive.getWeather(), 
                calculateDistance(mStartPoint, mEndPoint)
            );
            tvRecommendation.setText(recommendation);

            // 更新推荐的出行方式
            updateRecommendedMode(weatherLive.getWeather(), 
                calculateDistance(mStartPoint, mEndPoint));
        }
    });
}

总结:

上述的方法与代码仅供参考,当然这个项目并不完善,能修改的地方还有很多,例如:我们还可以添加一个出行方式的选择,驾车、骑车、步行、公交等等,由于篇幅有限这里就不赘述了。

} 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 “天气舒适,距离不远,非常适合步行。”;
}
}

// 默认建议
if (distance > 5) {
    return "距离较远,建议选择驾车出行。";
} else if (distance > 2) {
    return "距离适中,可以考虑骑行方式。";
} else {
    return "距离较近,建议步行前往。";
}

}




##### ②、calculateDistance 方法(计算距离)

```Java
private float calculateDistance(LatLng start, LatLng end) {
    try {
        if (end == null) {
            return 0;
        }

        // 获取当前位置作为起点
        Location currentLocation = aMap.getMyLocation();
        if (currentLocation == null) {
            if (start == null) {
                return 0;
            }
        } else {
            // 使用当前实际位置作为起点
            start = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
        }

        // 计算距离
        if (start != null && end != null) {
            float distance = AMapUtils.calculateLineDistance(start, end);
            return distance / 1000.0f; // 转换为公里
        }
        return 0;
    } catch (Exception e) {
        e.printStackTrace();
        return 0;
    }
}
③、onWeatherLiveSearched 方法中的智能建议部分
@Override
public void onWeatherLiveSearched(LocalWeatherLiveResult weatherLiveResult, int rCode) {
    requireActivity().runOnUiThread(() -> {
        if (rCode == AMapException.CODE_AMAP_SUCCESS) {
            LocalWeatherLive weatherLive = weatherLiveResult.getLiveResult();
            
            // 生成出行建议
            String recommendation = generateRecommendation(
                weatherLive.getWeather(), 
                calculateDistance(mStartPoint, mEndPoint)
            );
            tvRecommendation.setText(recommendation);

            // 更新推荐的出行方式
            updateRecommendedMode(weatherLive.getWeather(), 
                calculateDistance(mStartPoint, mEndPoint));
        }
    });
}

总结:

上述的方法与代码仅供参考,当然这个项目并不完善,能修改的地方还有很多,例如:我们还可以添加一个出行方式的选择,驾车、骑车、步行、公交等等,由于篇幅有限这里就不赘述了。

通过这个项目,对于高德地图SDK的使用,天气服务和数据处理,Android生命周期管理,异步编程和线程处理,错误处理和异常管理都有不小的收获,对于这些不熟悉的可以跟着做一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值