HarmonyOS智慧农业管理应用开发教程--高高种地---第5篇:地图首页与核心交互

第5篇:地图首页与核心交互

教程目标

通过本篇教程,你将学会:

  • 集成高德地图SDK
  • 配置地图API Key
  • 实现地图首页布局
  • 实现地图基础交互(缩放、平移、定位)
  • 实现定位服务
  • 设计地图工具栏

完成本教程后,你将拥有一个以地图为核心的应用首页。


一、高德地图SDK集成

1.1 添加SDK依赖

打开 oh-package.json5,在 dependencies 中添加高德地图相关依赖:

json5 复制代码
{
  "dependencies": {
    "@amap/amap_lbs_common": "^2.2.5",
    "@amap/amap_lbs_map3d": "^2.2.5",
    "@amap/amap_lbs_search": "^1.0.2",
    "@amap/amap_lbs_navi": "^1.0.0"
  }
}

1.2 安装依赖

在终端中执行:

bash 复制代码
ohpm install

等待依赖安装完成。

1.3 申请高德地图API Key

  1. 访问高德开放平台:https://lbs.amap.com/
  2. 注册并登录账号
  3. 进入"应用管理" → "我的应用"
  4. 点击"创建新应用"
  5. 填写应用信息
  6. 添加Key,选择"HarmonyOS"平台
  7. 填写应用包名:com.leson.zhongdi
  8. 获取API Key(格式如:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

重要提示:请妥善保管API Key,不要泄露。


二、配置地图常量

2.1 创建 MapConstants.ets

ets/constants/ 目录下创建 MapConstants.ets

typescript 复制代码
/**
 * 地图相关常量
 */
export class MapConstants {
  // 高德地图API Key(请替换为你自己的Key)
  static readonly AMAP_API_KEY = 'your_amap_api_key_here';

  // 默认地图中心点(北京天安门)
  static readonly DEFAULT_CENTER_LATITUDE = 39.9042;
  static readonly DEFAULT_CENTER_LONGITUDE = 116.4074;

  // 默认缩放级别
  static readonly DEFAULT_ZOOM = 12;

  // 地图类型
  static readonly MAP_TYPE_NORMAL = 1;  // 标准地图
  static readonly MAP_TYPE_SATELLITE = 2;  // 卫星地图

  // 定位相关
  static readonly LOCATION_TIMEOUT = 10000;  // 定位超时时间(毫秒)
  static readonly LOCATION_INTERVAL = 5000;  // 定位间隔(毫秒)
}

注意 :将 your_amap_api_key_here 替换为你申请的API Key。


三、创建地图数据模型

3.1 创建 MapModels.ets

ets/models/ 目录下创建 MapModels.ets

typescript 复制代码
/**
 * 地图相关数据模型
 */

/**
 * 地图位置
 */
export interface MapLocation {
  latitude: number;   // 纬度
  longitude: number;  // 经度
}

/**
 * 地图配置
 */
export interface MapViewConfig {
  center: MapLocation;
  zoom: number;
  mapType: number;
}

/**
 * 地块地图标记
 */
export interface FieldMapMarker {
  id: string;
  name: string;
  location: MapLocation;
  area: number;
  status: FieldMarkerStatus;
  color: string;
  description?: string;
}

/**
 * 地块标记状态
 */
export enum FieldMarkerStatus {
  PLANTING = 'planting',      // 种植中
  ATTENTION = 'attention',    // 需关注
  URGENT = 'urgent',          // 紧急
  IDLE = 'idle'               // 闲置
}

/**
 * 地图统计信息
 */
export interface MapStatistics {
  totalFields: number;
  plantingFields: number;
  idleFields: number;
  totalArea: number;
}

四、初始化地图SDK

4.1 在 EntryAbility 中初始化

打开 ets/entryability/EntryAbility.ets,添加地图SDK初始化:

typescript 复制代码
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { StorageUtil } from '../utils/StorageUtil';
import { ThemeManager } from '../utils/ThemeManager';
import { MapsInitializer } from '@amap/amap_lbs_map3d';
import { MapConstants } from '../constants/MapConstants';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'PlantingAssistant', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'PlantingAssistant', '%{public}s', 'Ability onDestroy');
  }

  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    hilog.info(0x0000, 'PlantingAssistant', '%{public}s', 'Ability onWindowStageCreate');

    // 初始化高德地图SDK
    try {
      MapsInitializer.setApiKey(MapConstants.AMAP_API_KEY);
      hilog.info(0x0000, 'PlantingAssistant', 'AMap SDK initialized with key');
    } catch (error) {
      hilog.error(0x0000, 'PlantingAssistant', 'Failed to initialize AMap SDK: %{public}s', JSON.stringify(error));
    }

    // 初始化存储
    try {
      await StorageUtil.init(this.context);
      hilog.info(0x0000, 'PlantingAssistant', 'StorageUtil initialized');
    } catch (error) {
      hilog.error(0x0000, 'PlantingAssistant', 'Failed to initialize StorageUtil: %{public}s', JSON.stringify(error));
    }

    // 初始化主题管理器
    try {
      await ThemeManager.getInstance().init();
      hilog.info(0x0000, 'PlantingAssistant', 'ThemeManager initialized');
    } catch (error) {
      hilog.error(0x0000, 'PlantingAssistant', 'Failed to initialize ThemeManager: %{public}s', JSON.stringify(error));
    }

    // 加载主页面
    windowStage.loadContent('pages/WelcomePage', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'PlantingAssistant', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(0x0000, 'PlantingAssistant', 'Succeeded in loading the content');
    });
  }

  onWindowStageDestroy(): void {
    hilog.info(0x0000, 'PlantingAssistant', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    hilog.info(0x0000, 'PlantingAssistant', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    hilog.info(0x0000, 'PlantingAssistant', '%{public}s', 'Ability onBackground');
  }
}

关键点

  • 使用 MapsInitializer.setApiKey() 设置API Key
  • 在应用启动时初始化,确保地图功能可用
  • 添加错误处理,记录初始化日志

五、创建地图服务

5.1 创建 AMapLocationService.ets

ets/services/ 目录下创建 AMapLocationService.ets

typescript 复制代码
/**
 * 高德定位服务
 * 提供GPS定位和逆地理编码功能
 */
import { common } from '@kit.AbilityKit';
import { abilityAccessCtrl, Permissions, PermissionRequestResult } from '@kit.AbilityKit';
import { geoLocationManager } from '@kit.LocationKit';

/**
 * 定位结果接口
 */
export interface LocationResult {
  latitude: number;
  longitude: number;
  address?: string;
  accuracy?: number;
  timestamp?: number;
}

/**
 * 定位回调接口
 */
export interface LocationCallback {
  onSuccess: (location: LocationResult) => void;
  onError: (error: string) => void;
}

export class AMapLocationService {
  private static instance: AMapLocationService;
  private context: common.UIAbilityContext | null = null;
  private hasLocationPermission: boolean = false;

  private constructor() {}

  static getInstance(): AMapLocationService {
    if (!AMapLocationService.instance) {
      AMapLocationService.instance = new AMapLocationService();
    }
    return AMapLocationService.instance;
  }

  /**
   * 初始化服务
   */
  initialize(context: common.UIAbilityContext): void {
    this.context = context;
    console.info('[AMapLocationService] Service initialized');
  }

  /**
   * 请求定位权限
   */
  async requestLocationPermission(): Promise<boolean> {
    if (!this.context) {
      console.error('[AMapLocationService] Context not initialized');
      return false;
    }

    try {
      const permissions: Array<Permissions> = [
        'ohos.permission.APPROXIMATELY_LOCATION',
        'ohos.permission.LOCATION'
      ];

      const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
      const data: PermissionRequestResult = await atManager.requestPermissionsFromUser(
        this.context,
        permissions
      );

      const grantStatus: Array<number> = data.authResults;
      let allGranted = true;

      for (let i = 0; i < grantStatus.length; i++) {
        if (grantStatus[i] !== 0) {
          allGranted = false;
          console.warn('[AMapLocationService] Permission denied:', permissions[i]);
        }
      }

      this.hasLocationPermission = allGranted;
      console.info('[AMapLocationService] Location permission:', allGranted ? 'granted' : 'denied');
      
      return allGranted;
    } catch (error) {
      console.error('[AMapLocationService] Request permission failed:', error);
      return false;
    }
  }

  /**
   * 获取当前位置
   */
  async getCurrentLocation(callback: LocationCallback): Promise<void> {
    if (!this.hasLocationPermission) {
      const granted = await this.requestLocationPermission();
      if (!granted) {
        callback.onError('定位权限未授予');
        return;
      }
    }

    try {
      const requestInfo: geoLocationManager.CurrentLocationRequest = {
        priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
        scenario: geoLocationManager.LocationRequestScenario.UNSET,
        maxAccuracy: 0
      };

      geoLocationManager.getCurrentLocation(requestInfo, (err, location) => {
        if (err) {
          console.error('[AMapLocationService] Get location failed:', JSON.stringify(err));
          callback.onError('定位失败');
          return;
        }

        const result: LocationResult = {
          latitude: location.latitude,
          longitude: location.longitude,
          accuracy: location.accuracy,
          timestamp: Date.now()
        };

        console.info('[AMapLocationService] Location obtained:', JSON.stringify(result));
        callback.onSuccess(result);
      });
    } catch (error) {
      console.error('[AMapLocationService] Get location error:', error);
      callback.onError('定位异常');
    }
  }
}

六、创建地图首页

6.1 创建 Map 目录

ets/pages/ 下创建 Map 目录。

6.2 创建 FieldMapPage.ets

Map/ 目录下创建 FieldMapPage.ets

typescript 复制代码
import { Map, MapOptions } from '@amap/amap_lbs_map3d';
import { AMapLocationService, LocationResult } from '../../services/AMapLocationService';
import { MapConstants } from '../../constants/MapConstants';
import { common } from '@kit.AbilityKit';

@Entry
@ComponentV2
struct FieldMapPage {
  @Local isLoading: boolean = true;
  @Local currentLocation: LocationResult | null = null;
  @Local mapReady: boolean = false;

  private locationService = AMapLocationService.getInstance();
  private context: common.UIAbilityContext | null = null;

  aboutToAppear(): void {
    this.context = getContext(this) as common.UIAbilityContext;
    this.locationService.initialize(this.context);
    this.requestLocation();
  }

  build() {
    Stack() {
      // 地图容器
      Column() {
        Map({
          mapOptions: this.getMapOptions(),
          onMapReady: () => {
            this.onMapReady();
          }
        })
        .width('100%')
        .height('100%')
      }

      // 工具栏
      Column() {
        this.buildToolbar()
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.End)
      .padding(16)

      // 加载提示
      if (this.isLoading) {
        Column() {
          LoadingProgress()
            .width(40)
            .height(40)
            .color($r('app.color.primary_professional'))
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#80000000')
      }
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 获取地图配置
   */
  private getMapOptions(): MapOptions {
    return {
      center: {
        latitude: MapConstants.DEFAULT_CENTER_LATITUDE,
        longitude: MapConstants.DEFAULT_CENTER_LONGITUDE
      },
      zoom: MapConstants.DEFAULT_ZOOM,
      mapType: MapConstants.MAP_TYPE_NORMAL
    };
  }

  /**
   * 地图准备完成回调
   */
  private onMapReady(): void {
    console.info('[FieldMapPage] Map ready');
    this.mapReady = true;
    this.isLoading = false;
  }

  /**
   * 请求定位
   */
  private async requestLocation(): Promise<void> {
    await this.locationService.getCurrentLocation({
      onSuccess: (location: LocationResult) => {
        console.info('[FieldMapPage] Location success:', JSON.stringify(location));
        this.currentLocation = location;
        this.isLoading = false;
      },
      onError: (error: string) => {
        console.error('[FieldMapPage] Location error:', error);
        this.isLoading = false;
      }
    });
  }

  /**
   * 构建工具栏
   */
  @Builder
  buildToolbar() {
    Column({ space: 12 }) {
      // 定位按钮
      Button() {
        Text('📍')
          .fontSize(24)
      }
      .width(48)
      .height(48)
      .backgroundColor($r('app.color.card_background'))
      .borderRadius(24)
      .onClick(() => {
        this.onLocationClick();
      })

      // 地图类型切换
      Button() {
        Text('🗺️')
          .fontSize(24)
      }
      .width(48)
      .height(48)
      .backgroundColor($r('app.color.card_background'))
      .borderRadius(24)
      .onClick(() => {
        this.onMapTypeClick();
      })

      // 添加地块
      Button() {
        Text('➕')
          .fontSize(24)
      }
      .width(48)
      .height(48)
      .backgroundColor($r('app.color.primary_professional'))
      .borderRadius(24)
      .onClick(() => {
        this.onAddFieldClick();
      })
    }
    .alignItems(HorizontalAlign.End)
  }

  private onLocationClick(): void {
    console.info('[FieldMapPage] Location button clicked');
    this.requestLocation();
  }

  private onMapTypeClick(): void {
    console.info('[FieldMapPage] Map type button clicked');
  }

  private onAddFieldClick(): void {
    console.info('[FieldMapPage] Add field button clicked');
  }
}

6.3 配置页面路由

打开 main_pages.json,添加:

json 复制代码
{
  "src": [
    "pages/WelcomePage",
    "pages/Index",
    "pages/Map/FieldMapPage",
    "pages/OnboardingFlow/ModeSelectionPage",
    "pages/OnboardingFlow/LocationPage",
    "pages/OnboardingFlow/GoalsPage"
  ]
}

七、运行与测试

7.1 测试步骤

  1. 清除应用数据
  2. 运行应用
  3. 完成引导流程
  4. 进入地图页面
  5. 授予定位权限
  6. 观察地图加载

预期效果

  • ✅ 地图正常显示
  • ✅ 定位权限请求正常
  • ✅ 工具栏按钮显示
  • ✅ 点击按钮有响应

八、总结

本篇教程完成了:

  • ✅ 高德地图SDK集成
  • ✅ 定位服务实现
  • ✅ 地图首页布局
  • ✅ 基础交互功能

九、下一步

下一篇将学习地块管理系统的数据模型与列表实现。


教程版本 :v1.0
更新日期 :2026-01
适用版本:DevEco Studio 5.0+, HarmonyOS API 17+

相关推荐
不会写代码0002 小时前
Flutter 框架跨平台鸿蒙开发 - 照片水印添加器开发教程
flutter·华为·harmonyos
南村群童欺我老无力.2 小时前
Flutter 框架跨平台鸿蒙开发 - 大写数字转换器应用开发教程
flutter·华为·harmonyos
全栈开发圈13 小时前
干货分享|鸿蒙6开发从0到App上线
华为·harmonyos
奔跑的露西ly14 小时前
【HarmonyOS NEXT】ArkUI实现「单格单字符+下划线」手机号/验证码输入框
ui·华为·harmonyos·鸿蒙
木斯佳14 小时前
HarmonyOS实战(人机交互篇):当ToB系统开始“思考”,我们该设计什么样的界面?
华为·人机交互·harmonyos
夜雨声烦丿15 小时前
Flutter 框架跨平台鸿蒙开发 - 中英互译助手 - 完整开发教程
flutter·华为·harmonyos
No Silver Bullet16 小时前
HarmonyOS NEXT开发进阶(二十一):compatibleSdkVersion 和 targetSdkVersion配置项详解
华为·harmonyos
小白阿龙17 小时前
鸿蒙+flutter 跨平台开发——图像编解码与水印嵌入技术实战
flutter·华为·harmonyos·鸿蒙
哈哈你是真的厉害17 小时前
基础入门 React Native 鸿蒙跨平台开发:ActionSheet 动作面板
react native·react.js·harmonyos