HarmonyOS NEXT 鸿蒙ArkTS 真机测试 地图定位功能实现,AGC签名证书,文本转语音功能,旅游指导~

248 阅读6分钟

map.webp

一、四签名拿到client ID

前提准备工作

image.png

在DevEco Studio上外面可以获得两个证书

image.png

注意:在DevEco Studio上刚才申请两个证书的账号和密码一定要记住,我是放在了新建文件夹里

image.png

这个是第三个证书, 然后再绑定应用获得AGC平台颁发的p7b证书,这样我们就可以拿到client ID了(注意我们要拿的是应用的ID),记得点击添加公钥指纹

image.png

点击下载获得第四个证书

image.png

勾上这些API,然后在DevEco Studio配置ID和证书

image.png

image.png

这个是写在modules.json5

二、location kit 和 map kit

功能优点:用户可以查看自己的位置,和景点的位置(marker标记),详细地图方便了解具体位置

这里需要打开定位权限,需要权限控制模板的可以查看我上一篇文章

image.png

第一个是精准定位以米级别的,模糊定位是5公里级别的,我们这个权限是user——grant级别的,这个需要弹窗授权,框口中可以设置精准定位还是模糊定位(当然这里还有一个后台定位的权限,这里暂时不需要)

  • 这个时候我们就可以通过geoLocationManager这个API获得自身的定位信息了
  • 关于定位信息我这边给一个稍微详细点的代码
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'
import { geoLocationManager } from '@kit.LocationKit'
import json from '@ohos.util.json'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  @State isGrant:boolean = false
  @State location:geoLocationManager.Location | null = null
  async aboutToAppear(){
    this.isGrant = await this.requestPermissions(["ohos.permission.LOCATION","ohos.permission.APPROXIMATELY_LOCATION"])
  }
  async requestPermissions(permissions:Permissions[]){
    const atManager = abilityAccessCtrl.createAtManager()
    const requestResult = await atManager.requestPermissionsFromUser(getContext(),permissions)
    const isAuth=requestResult.authResults.every(item=>item===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
    return isAuth === true ? Promise.resolve(true) : Promise.reject(false)
  }
  build() {
    Navigation(){
      Column({space: 10}){
        Text('位置服务是否开启:'+this.isGrant)
        Text('当前的位置信息:'+json.stringify(this.location,null,2))
        Button('获取当前位置').onClick(async ()=>{
          try{
            this.location = await geoLocationManager.getCurrentLocation()
          }catch(err){
            promptAction.showToast({message:'请再控制中心,开启定位服务'})
          }
        })
        Button('开启位置变化订阅').onClick(()=>{
          geoLocationManager.on('locationChange',
            {
              priority: geoLocationManager.LocationRequestPriority.ACCURACY,//精度优先,卫星定位
              scenario: geoLocationManager.LocationRequestScenario.NAVIGATION,//导航场景,卫星定位
              timeInterval: 1000,//1s
              distanceInterval: 10,//10m
            },
            (location)=>{this.location=location})
        })
        Button('关闭订阅').onClick(()=>{
          geoLocationManager.off('locationChange')
        })
        Button('判断地理编码与逆地理编码是否可用').onClick(()=>{
          const isAvailable = geoLocationManager.isGeocoderAvailable()
          AlertDialog.show({ message:'是否支持:'+isAvailable})
        })
        Button('逆地理编码(逆解析)-坐标转位置').onClick(async ()=>{
          const addresses = await geoLocationManager.getAddressesFromLocation({locale:'zh',latitude: 30.584355,longitude: 114.298572})//maxItems:1
          AlertDialog.show({ message:JSON.stringify(addresses,null,2)})
        })
        Button('地理编码-位置转坐标').onClick(async ()=>{
          const location = await geoLocationManager.getAddressesFromLocationName({description:'北京'})
          AlertDialog.show({ message:JSON.stringify(location,null,2)})
        })
      }
      .constraintSize({minHeight:'100%'})
    }
    .title('Location Kit 位置服务')
    .titleMode(NavigationTitleMode.Mini)
  }
}

注意:打开地图有几个前提:

  • 1.四签名
  • 2.应用的client ID
  • 3.AGC平台勾选相关API
  • 4.真机的UDID添加到AGC平台的设备中(差点漏了这个知识点,下面是获取真机UDID的步骤)

image.png

①打开这个文件路径,找那个最新的文件夹,然后点开里面的toolchains文件
②打开CDM终端复制hdc shell bm get --udid回车,复制UDID,在AGC平台添加设备

官方文档上面有一套map的模板代码,我们这边调整了一下,比如使用Marker标记旅游的地点,打开定位按钮。

import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { geoLocationManager } from '@kit.LocationKit';
@Entry
@Component
struct HuaweiMapDemo {
  private TAG = "HuaweiMapDemo";
  private mapOptions?: mapCommon.MapOptions;
  private callback?: AsyncCallback<map.MapComponentController>;
  private mapController?: map.MapComponentController;
  private mapEventManager?: map.MapEventManager;
  private marker?: map.Marker;

  async aboutToAppear(){
    this.requestPermissions(["ohos.permission.LOCATION","ohos.permission.APPROXIMATELY_LOCATION"])
    // 地图初始化参数,设置地图中心点坐标及层级
    this.mapOptions = {
      position: {
        target: {
          latitude: 22.19,
          longitude: 114.11
        },
        zoom: 10
      },
      myLocationControlsEnabled: true,
      alwaysShowScaleEnabled:true
    };

    // 地图初始化的回调
    this.callback = async (err, mapController) => {
      if (!err) {
        // 获取地图的控制器类,用来操作地图
        this.mapController = mapController;
        let location = await geoLocationManager.getCurrentLocation();
        this.mapController.setMyLocation(location);
        // 启用我的位置图层
        this.mapController.setMyLocationEnabled(true);
        // 启用我的位置按钮
        this.mapController.setMyLocationControlsEnabled(true);
        // let style: mapCommon.MyLocationStyle = {
        //   anchorU: 0.5,
        //   anchorV: 0.5,
        //   radiusFillColor: 0xffff0000,
        //   // icon为自定义图标资源,使用时需要替换
        //   // 图标存放在resources/rawfile,icon参数传入rawfile文件夹下的相对路径
        //   icon: 'images/logo.png'
        // };
        // await this.mapController.setMyLocationStyle(style);

        this.mapEventManager = this.mapController.getEventManager();
        //this.mapEventManager.on("myLocationButtonClick", callback);
        // Marker初始化参数
        let markerOptions: mapCommon.MarkerOptions = {
          position: {
            latitude: 22.64,
            longitude: 113.92
          },
          rotation: 0,
          visible: true,
          zIndex: 0,
          alpha: 1,
          anchorU: 0.5,
          anchorV: 1,
          clickable: true,
          draggable: true,
          flat: false,
          // 图标存放在resources/rawfile,icon参数传入rawfile文件夹下的相对路径
          icon: 'images/head.png'
        };
        // 创建Marker
        this.marker = await this.mapController.addMarker(markerOptions);
        // 设置信息窗的标题
        this.marker.setTitle('深圳');
        // 设置信息窗的子标题
        this.marker.setSnippet('小黑程序员');
        // 设置标记可点击
        this.marker.setClickable(true);
        // 设置信息窗的锚点位置
        this.marker.setInfoWindowAnchor(1, 1);
        // 设置信息窗可见
        this.marker.setInfoWindowVisible(true);
        // 地图加载完成事件
        // let callback = () => {
        //   console.info(this.TAG, `on-mapLoad`);
        // }
        // this.mapEventManager.on("mapLoad", callback);

      }
    };
    // let markerOptions:mapCommon.MarkerOptions = {
    //   position:{latitude:39.9,longitude:116.4}
    // }
    // let marker:map.Marker = await this.mapController?.addMarker(markerOptions)
    // marker.
  }
  async requestPermissions(permissions:Permissions[]){
    const atManager = abilityAccessCtrl.createAtManager()
    const requestResult = await atManager.requestPermissionsFromUser(getContext(),permissions)
    const isAuth=requestResult.authResults.every(item=>item===abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
    return isAuth === true ? Promise.resolve(true) : Promise.reject(false)
  }

  // 页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效
  onPageShow(): void {
    // 将地图切换到前台
    if (this.mapController) {
      this.mapController.show();
    }
  }

  // 页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效
  onPageHide(): void {
    // 将地图切换到后台
    if (this.mapController) {
      this.mapController.hide();
    }
  }

  build() {
    Stack() {
      // 调用MapComponent组件初始化地图
      MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback }).width('100%').height('100%');
    }.height('100%')
  }
}

三、文本转语音功能

功能优点:一个旅游景点会有一些相关的介绍,使用文本转语音功能使用户更加方便了解景点的相关信息

这里我们使用了相关的APItextToSpeech来创建引擎然后使用textToSpeech.TextToSpeechEngine里面的setListener方法来播放文本
注意:每次播放完毕后要使用shutdown方法释放引擎


let ttsEngine: textToSpeech.TextToSpeechEngine;
@Entry
...

// 创建引擎,通过callback形式返回
private createByCallback() {
  // 设置创建引擎参数
  let extraParam: Record<string, Object> = {"style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName'};
  let initParamsInfo: textToSpeech.CreateEngineParams = {
    language: 'zh-CN',
    person: 0,
    online: 1,
    extraParams: extraParam
  };

  // 调用createEngine方法
  textToSpeech.createEngine(initParamsInfo, (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {
    if (!err) {
      console.info('Succeeded in creating engine.');
      // 接收创建引擎的实例
      ttsEngine = textToSpeechEngine;
    } else {
      console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);
    }
  });
};

// 调用speak播报方法
private speak() {
  let speakListener: textToSpeech.SpeakListener = {
    // 开始播报回调
    onStart(requestId: string, response: textToSpeech.StartResponse) {
      console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    },
    // 完成播报回调
    onComplete(requestId: string, response: textToSpeech.CompleteResponse) {

      console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    },
    // 停止播报完成回调,调用stop方法并完成时会触发此回调
    onStop(requestId: string, response: textToSpeech.StopResponse) {
      console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);
    },
    // 返回音频流
    onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {
      console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);
    },
    // 错误回调,播报过程发生错误时触发此回调
    onError(requestId: string, errorCode: number, errorMessage: string) {
      console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
    }
  };
  // 设置回调
  ttsEngine.setListener(speakListener);
  // 设置播报相关参数
  let extraParam: Record<string, Object> = {"queueMode": 0, "speed": 1, "volume": 2, "pitch": 1, "languageContext": 'zh-CN', "audioType": "pcm", "soundChannel": 3, "playType":1}
  let speakParams: textToSpeech.SpeakParams = {
    requestId: '123456-a', // requestId在同一实例内仅能用一次,请勿重复设置
    extraParams: extraParam
  };
  // 调用speak播报方法
  ttsEngine.speak(this.originalText, speakParams);
};

这里面的this.originalText使用@State 状态变量来管理,当用户点击某个文本时复制成那个文本就可以了,是string类型的

  • 核心:
  • this.createByCallback()创建引擎
  • this.speak()文本转语音播放
  • this.createByCallback()释放

<这里MP4格式的视频传不了,语音功能效果不错,还有其他的语音相关调整功能,查询语种音色信息等,相关API和方法可以在官网上查询>

四、搞个AI小助手

功能优点:用户不懂的问题可以直接问AI,畅所欲言,协同旅行

UIAbility跳转,跳转页面可以查看我之前的文章,使用的是deepseek-v3的接口,接口很多,可以自己去搭建

.onClick(() => {
  const context = getContext() as common.UIAbilityContext
  context.startAbility({
    bundleName: 'com.example.myapplication1_my_map_kit',
    moduleName: 'entry',
    abilityName: 'EntryAbilityAI'
  })
})

评选规则用的rawfile路径,html页面,使用了webview的API
controller:new webview.WebviewController()

import { webview } from '@kit.ArkWeb'
@Entry
@Component
struct Index {
  webctrl = new webview.WebviewController()
  build() {
    Column(){
      Web({
        src:$rawfile('mypages/index.html'),
        controller: this.webctrl
      })
    }
    .height('100%')
    .width('100%')
  }
}