[MAUI]集成高德地图组件至.NET MAUI Blazor项目

文章目录

    • 前期准备:注册高德开发者并创建 key
      • 登录控制台
      • 创建 key
      • 获取 key 和密钥
    • 创建项目
      • 创建JS API Loader
      • 配置权限
      • 创建定义
      • 创建模型
      • 创建地图组件
      • 创建交互逻辑
    • 项目地址

地图组件在手机App中常用地理相关业务,如查看线下门店,设置导航,或选取地址等。是一个较为常见的组件。

在.NET MAUI 中,有两种方案可以集成高德地图,一种是使用原生库绑定。网上也有人实现过:https://blog.csdn.net/sD7O95O/article/details/125827031

但这种方案需要大量平台原生开发的知识,而且需要对每一个平台进行适配。

在这里我介绍第二种方案:.NET MAUI Blazor + 高德地图JS API 2.0 库的实现。

JS API 2.0 是高德开放平台基于WebGL的地图组件,可以将高德地图模块集成到.NET MAUI Blazor中的BlazorWebView控件,由于BlazorWebView的跨平台特性,可以达到一次开发全平台通用,无需为每个平台做适配。

今天用此方法实现一个地图选择器,使用手机的GPS定位初始化当前位置,使用高德地图JS API库实现地点选择功能。混合开发方案涉及本机代码与JS runtime的交互,如果你对这一部分还不太了解,可以先阅读这篇文章:[MAUI]深入了解.NET MAUI Blazor与Vue的混合开发

.NET MAUI Blazor

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。

前期准备:注册高德开发者并创建 key

登录控制台

登录 高德开放平台控制台,如果没有开发者账号,请 注册开发者。

在这里插入图片描述

创建 key

进入应用管理,创建新应用,新应用中添加 key,服务平台选择 Web端(JS API)。再创建一个Web服务类型的Key,用于解析初始位置地址。

在这里插入图片描述

获取 key 和密钥

创建成功后,可获取 key 和安全密钥。

在这里插入图片描述

创建项目

新建.NET MAUI Blazor项目,命名AMap

创建JS API Loader

前往https://webapi.amap.com/loader.js另存js文件至项目wwwroot文件夹

在这里插入图片描述

在wwwroot创建amap_index.html文件,将loader.js引用到页面中。创建_AMapSecurityConfig对象并设置安全密钥。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" /><title>AmapApp</title><base href="/" /><link href="css/app2.css" rel="stylesheet" />
</head><body><div class="status-bar-safe-area"></div><div id="app">Loading...</div><div id="blazor-error-ui">An unhandled error has occurred.<a href="" class="reload">Reload</a><a class="dismiss">🗙</a></div><script src="_framework/blazor.webview.js" autostart="false"></script><script src="lib/amap/loader.js"></script><script type="text/javascript">window._AMapSecurityConfig = {securityJsCode: "764832459a38e824a0d555b62d8ec1f0",};</script></body></html>

配置权限

打开Android端AndroidManifest.xml文件

在这里插入图片描述

添加权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

打开Info.plist文件,添加权限描述信心

在这里插入图片描述

<key>NSLocationWhenInUseUsageDescription</key>
<string>允许使用设备的GPS更新您的位置信息。</string>

创建定义

创建Position,Poi,Location等类型,用于描述位置信息。由于篇幅这里不展开介绍。

创建模型

创建一个MainPageViewModel类,用于处理页面逻辑。代码如下:

public class MainPageViewModel : ObservableObject
{public event EventHandler<FinishedChooiseEvenArgs> OnFinishedChooise;private static AsyncLock asyncLock = new AsyncLock();public static RateLimitedAction throttledAction = Debouncer.Debounce(null, TimeSpan.FromMilliseconds(1500), leading: false, trailing: true);public MainPageViewModel(){Search = new Command(SearchAction);Done = new Command(DoneAction);Remove = new Command(RemoveAction);}private void RemoveAction(object obj){this.Address=null;this.CurrentLocation=null;OnFinishedChooise?.Invoke(this, new FinishedChooiseEvenArgs(Address, CurrentLocation));}private void DoneAction(object obj){OnFinishedChooise?.Invoke(this, new FinishedChooiseEvenArgs(Address, CurrentLocation));}private void SearchAction(object obj){Init();}public async void Init(){var location = await GeoLocationHelper.GetNativePosition();if (location==null){return;}var amapLocation = new Location.Location(){Latitude=location.Latitude,Longitude=location.Longitude};CurrentLocation=amapLocation;}private Location.Location _currentLocation;public Location.Location CurrentLocation{get { return _currentLocation; }set{if (_currentLocation != value){if (value!=null &&_currentLocation!=null&&Location.Location.CalcDistance(value, _currentLocation)<100){return;}_currentLocation = value;OnPropertyChanged();}}}private string _address;public string Address{get { return _address; }set{_address = value;OnPropertyChanged();}}private ObservableCollection<Poi> _pois;public ObservableCollection<Poi> Pois{get { return _pois; }set{_pois = value;OnPropertyChanged();}}private Poi _selectedPoi;public Poi SelectedPoi{get { return _selectedPoi; }set{_selectedPoi = value;OnPropertyChanged();}}public Command Search { get; set; }public Command Done { get; set; }public Command Remove { get; set; }}

注意这里的Init方法,用于初始化位置。

GeoLocationHelper.GetNativePosition()方法用于从你设备的GPS模块,获取当前位置。它调用的是Microsoft.Maui.Devices.Sensors提供的设备传感器访问功能
,详情可参考官方文档地理位置 - .NET MAUI

创建地图组件

创建Blazor页面AMapPage.razor以及AMapPage.razor.js

AMapPage.razor中引入

protected override async Task OnAfterRenderAsync(bool firstRender)
{if (!firstRender)return;await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./AMapPage.razor.js");await Refresh();await JSRuntime.InvokeVoidAsync("window.initObjRef", this.objRef);
}

razor页面的 @Code 代码段中,放置MainPageViewModel属性,以及一个DotNetObjectReference对象,用于在JS中调用C#方法。

@code {[Parameter]public MainPageViewModel MainPageViewModel { get; set; }private DotNetObjectReference<AMapPage> objRef;protected override void OnInitialized(){objRef = DotNetObjectReference.Create(this);}private async Task Refresh(){...}

AMapPage.razor.js我们加载地图,并设置地图的中心点。和一些地图挂件。此外,我们还需要监听地图的中心点变化,更新中心点。 这些代码可以从官方示例中复制。(https://lbs.amap.com/demo/javascript-api-v2/example/map/map-moving)。

console.info("start load")
window.viewService = {map: null,zoom: 13,amaplocation: [116.397428, 39.90923],SetAmapContainerSize: function (width, height) {console.info("setting container size")var div = document.getElementById("container");div.style.height = height + "px";},SetLocation: function (longitude, latitude) {console.info("setting loc", longitude, latitude)window.viewService.amaplocation = [longitude, latitude];if (window.viewService.map) {window.viewService.map.setZoomAndCenter(window.viewService.zoom, window.viewService.amaplocation);console.info("set loc", window.viewService.zoom, window.viewService.map)}},isHotspot: true}
AMapLoader.load({ //首次调用 loadkey: '0896cedc056413f83ca0aee5b029c65d',//首次load key为必填version: '2.0',plugins: ['AMap.Scale', 'AMap.ToolBar', 'AMap.InfoWindow', 'AMap.PlaceSearch']
}).then((AMap) => {console.info("loading..")var opt = {resizeEnable: true,center: window.viewService.amaplocation,zoom: window.viewService.zoom,isHotspot: true}var map = new AMap.Map('container', opt);console.info(AMap, map, opt)map.addControl(new AMap.Scale())map.addControl(new AMap.ToolBar())window.viewService.marker = new AMap.Marker({position: map.getCenter()})map.add(window.viewService.marker);var placeSearch = new AMap.PlaceSearch();  //构造地点查询类var infoWindow = new AMap.InfoWindow({});map.on('hotspotover', function (result) {placeSearch.getDetails(result.id, function (status, result) {if (status === 'complete' && result.info === 'OK') {onPlaceSearch(result);}});});map.on('moveend', onMapMoveend);// map.on('zoomend', onMapMoveend);//回调函数window.viewService.map = map;function onMapMoveend() {var zoom = window.viewService.map.getZoom(); //获取当前地图级别var center = window.viewService.map.getCenter(); //获取当前地图中心位置if (window.viewService.marker) {window.viewService.marker.setPosition(center);}window.objRef.invokeMethodAsync('OnMapMoveend', center);}function onPlaceSearch(data) { //infoWindow.open(map, result.lnglat);var poiArr = data.poiList.pois;if (poiArr[0]) {var location = poiArr[0].location;infoWindow.setContent(createContent(poiArr[0]));infoWindow.open(window.viewService.map, location);}}function createContent(poi) {  //信息窗体内容var s = [];s.push('<div class="info-title">' + poi.name + '</div><div class="info-content">' + "地址:" + poi.address);s.push("电话:" + poi.tel);s.push("类型:" + poi.type);s.push('<div>');return s.join("<br>");}console.info("loaded")}).catch((e) => {console.error(e);
});
window.initObjRef = function (objRef) {window.objRef = objRef;
}

地图中心点改变时,我们需要使用window.objRef.invokeMethodAsync('OnMapMoveend', center);从JS runtime中通知到C#代码。

同时,在AMapPage.razor中配置一个方法,用于接收从JS runtime发来的回调通知。
在此赋值CurrentLocation属性。


[JSInvokable]
public async Task OnMapMoveend(dynamic location)
{await Task.Run(() =>{var locationArray = JsonConvert.DeserializeObject<double[]>(location.ToString());MainPageViewModel.CurrentLocation=new Location.Location(){Longitude=locationArray[0],Latitude =locationArray[1]};});
}

同时监听CurrentLocation属性的值,一旦发生变化,则调用JS runtime中的viewService.SetLocation方法,更新地图中心点。

protected override async Task OnInitializedAsync()
{MainPageViewModel.PropertyChanged +=  async (o, e) =>{if (e.PropertyName==nameof(MainPageViewModel.CurrentLocation)){if (MainPageViewModel.CurrentLocation!=null){var longitude = MainPageViewModel.CurrentLocation.Longitude;var latitude = MainPageViewModel.CurrentLocation.Latitude;await JSRuntime.InvokeVoidAsync("viewService.SetLocation", longitude, latitude);}}};}

MainPageViewModel类中,我们添加一个PropertyChanged事件,用于监听CurrentLocation属性的改变。

当手指滑动地图触发位置变化,导致CurrentLocation属性改变时,将当前的中心点转换为具体的地址。这里使用了高德逆地理编码API服务(https://restapi.amap.com/v3/geocode/regeo)解析CurrentLocation的值, 还需使用了防抖策略,避免接口的频繁调用。


public MainPageViewModel()
{PropertyChanged+=MainPageViewModel_PropertyChanged;...
}private async void MainPageViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{if (e.PropertyName == nameof(CurrentLocation)){if (CurrentLocation!=null){// 使用防抖using (await asyncLock.LockAsync()){var amapLocation = new Location.Location(){Latitude=CurrentLocation.Latitude,Longitude=CurrentLocation.Longitude};var amapInverseHttpRequestParamter = new AmapInverseHttpRequestParamter(){Locations= new Location.Location[] { amapLocation }};ReGeocodeLocation reGeocodeLocation = null;try{reGeocodeLocation = await amapHttpRequestClient.InverseAsync(amapInverseHttpRequestParamter);}catch (Exception ex){Console.WriteLine(ex.ToString());}throttledAction.Update(() =>{MainThread.BeginInvokeOnMainThread(() =>{CurrentLocation=amapLocation;if (reGeocodeLocation!=null){Address = reGeocodeLocation.Address;Pois=new ObservableCollection<Poi>(reGeocodeLocation.Pois);}});});throttledAction.Invoke();}}}
}

至此我们完成了地图组件的基本功能。

创建交互逻辑

在MainPage.xaml中,创建一个选择器按钮,以及一个卡片模拟选择器按钮点击后的弹窗。


<Button Clicked="Button_Clicked"Grid.Row="1"x:Name="SelectorButton"HorizontalOptions="Center"VerticalOptions="Center"Text="{Binding Address, TargetNullValue=请选择地点}"></Button><Border StrokeShape="RoundRectangle 10"Grid.RowSpan="2"x:Name="SelectorPopup"IsVisible="False"Margin="5,50"MinimumHeightRequest="500"><Grid Padding="0"><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition Height="Auto"></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><Grid Grid.Row="0"><Grid.ColumnDefinitions><ColumnDefinition Width="*"></ColumnDefinition><ColumnDefinition></ColumnDefinition></Grid.ColumnDefinitions><Label FontSize="Large"Margin="10, 10, 10, 0"FontAttributes="Bold"Text="选择地点"></Label><HorizontalStackLayout Grid.Column="1"HorizontalOptions="End"><Button Text="删除"Margin="5,0"Command="{Binding Remove}"></Button><Button Text="完成"Margin="5,0"Command="{Binding Done}"></Button></HorizontalStackLayout></Grid><Grid Grid.Row="1"Margin="10, 10, 10, 0"><Grid.RowDefinitions><RowDefinition></RowDefinition><RowDefinition Height="Auto"></RowDefinition></Grid.RowDefinitions><Label HorizontalTextAlignment="Center"VerticalOptions="Center"x:Name="ContentLabel"Text="{Binding Address}"></Label><Border IsVisible="False"Grid.RowSpan="2"x:Name="ContentFrame"><Entry Text="{Binding Address, Mode=TwoWay}"Placeholder="请输入地址, 按Enter键完成"Completed="Entry_Completed"Unfocused="Entry_Unfocused"ClearButtonVisibility="WhileEditing"></Entry></Border><Border x:Name="ContentButton"Grid.Row="1"HorizontalOptions="Center"VerticalOptions="Center"><Label><Label.FormattedText><FormattedString><Span FontFamily="FontAwesome"Text="&#xf044;"></Span><Span Text=" 修改"></Span></FormattedString></Label.FormattedText></Label><Border.GestureRecognizers><TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer></Border.GestureRecognizers></Border></Grid><BlazorWebView Grid.Row="2"Margin="-10, 0"x:Name="mainMapBlazorWebView"HostPage="wwwroot/amap_index.html"><BlazorWebView.RootComponents><RootComponent Selector="#app"x:Name="rootComponent"ComponentType="{x:Type views:AMapPage}" /></BlazorWebView.RootComponents></BlazorWebView></Grid>
</Border>

在这里插入图片描述

最终效果如下:

在这里插入图片描述

项目地址

Github:maui-samples

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/284261.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Redis的Sentinel机制

Sentinel简述 Sentinel为了解决什么问题&#xff1f; Sentinel&#xff08;哨岗、哨兵&#xff09;是Redis的高可用性&#xff08;high availability&#xff09;解决方案。 我们知道Redis 的主从复制模式可以将主节点的数据改变同步给从节点&#xff0c;这样从节点就可以起…

有什么可以下载网页视频的浏览器插件 浏览器如何下载网页视频 网页视频怎么下载到本地 网页视频下载软件 IDM下载

在视频网站上看电影追剧&#xff0c;已经成为了大众生活中必不可少的一部分。为了保护自家视频的版权&#xff0c;很多平台都禁止用户下载会员视频。其实只要掌握了正确的方法&#xff0c;一样可以将会员视频下载到本地保存。那么有关有什么可以下载网页视频的浏览器&#xff0…

windows安装ssh

一、下载ssh https://github.com/PowerShell/Win32-OpenSSH/releases/download/v8.1.0.0p1-Beta/OpenSSH-Win64.zip 二、安装ssh 解压到C:\Program Files\OpenSSH-Win64 配置环境变量 把 C:\Program Files\OpenSSH-Win64 加到path环境变量里面 C:\Program Files\OpenSSH-Win64&…

C语言经典算法-9

文章目录 其他经典例题跳转链接46.稀疏矩阵47.多维矩阵转一维矩阵48.上三角、下三角、对称矩阵49.奇数魔方阵50.4N 魔方阵51.2(2N1) 魔方阵 其他经典例题跳转链接 C语言经典算法-1 1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官&#xff08;一&#xff09;6.…

iOS UIFont-新增第三方字体

背景 在项目中添加三方字体&#xff0c;是在开发中比较常见的需求&#xff0c;每次新增字体&#xff0c;都会遗忘其中某个步骤&#xff0c;又要去百度一下才能把字体添加使用成功。每次这样有点浪费时间和打击自信&#xff0c;于是便想着&#xff0c;自己好好来理一理新增字体…

腾讯云服务器如何购买?图文全流程,2024最新整理

腾讯云服务器购买流程很简单&#xff0c;有两种购买方式&#xff0c;直接在官方活动上购买比较划算&#xff0c;在云服务器CVM或轻量应用服务器页面自定义购买价格比较贵&#xff0c;但是自定义购买云服务器CPU内存带宽配置选择范围广&#xff0c;活动上购买只能选择固定的活动…

隐私计算实训营学习三:隐私计算框架的架构和技术要点

文章目录 一、隐语架构二、产品层三、算法层3.1 PSI与PIR3.2 Data Analysis-SCQL3.3 Federated Learning 四、计算层4.1 混合调度编译-RayFed4.2 密态引擎4.3 密码原语YACL 五、资源管理层六、互联互通七、跨域管控 一、隐语架构 1、完备性&#xff1a;支持多种技术&#xff0…

Git Commit 提交规范,变更日志、版本发布自动化和 Emoji 提交标准

前言 Git Commit 是开发的日常操作, 一个优秀的 Commit Message 不仅有助于他人 Review, 还可以有效的输出 CHANGELOG, 对项目的管理实际至关重要, 但是实际工作中却常常被大家忽略&#xff0c;希望通过本文&#xff0c;能够帮助大家规范 Git Commit&#xff0c;并且展示相关 …

git基础-查看提交历史

查看提交历史 在创建了多个提交之后&#xff0c;或者如果克隆了一个具有现有提交历史的存储库&#xff0c;可能会想要回顾一下发生了什么。最基本和强大的工具就是 git log 命令。 运行下git log查看下输出状态 默认情况下&#xff0c;不带任何参数运行 git log 命令会以逆时…

Linux的学习之路:2、基础指令(1)

一、ls指令 上篇文章已经说了一点点的ls指令&#xff0c;不过那还是不够的&#xff0c;这篇文章会介绍更多的指令&#xff0c;最起码能使用命令行进行一些简单的操作&#xff0c;下面开始介绍了 ls常用选项 -a 列出目录下的所有文件&#xff0c;包括以 . 开头的隐含文件。 -d…

AcWing 796. 子矩阵的和

这个题的重点是仿照一维的数组&#xff0c;所以a[N][N]也是从1索引开始的。画个图举个例子就非常清晰了 之所以不好理解是因为没画格子&#xff0c;一个格子代表一个点&#xff0c;就很好理解了。 java代码&#xff1a; import java.io.*; public class Main{static int N 1…

Typecho如何去掉/隐藏index.php

Typecho后台设置永久链接后&#xff0c;会在域名后加上index.php&#xff0c;很多人都接受不了。例如如下网址&#xff1a;https://www.jichun29.cn/index.php/archives/37/&#xff0c;但我们希望最终的形式是这样&#xff1a;https://www.jichun29.cn/archives/37.html。那么…

JMeter并发工具的使用

视频地址&#xff1a;Jmeter安装教程01_Jmeter之安装以及环境变量配置_哔哩哔哩_bilibili 一、JMeter是什么 JMeter是一款免安装包&#xff0c;官网下载好后直接解压缩并配置好环境变量就可以使用。 环境变量配置可参考&#xff1a;https://www.cnblogs.com/liulinghua90/p/…

挖掘网络宝藏:利用Scala和Fetch库下载Facebook网页内容

介绍 在数据驱动的世界里&#xff0c;网络爬虫技术是获取和分析网络信息的重要工具。本文将探讨如何使用Scala语言和Fetch库来下载Facebook网页内容。我们还将讨论如何通过代理IP技术绕过网络限制&#xff0c;以爬虫代理服务为例。 技术分析 Scala是一种多范式编程语言&…

尽可能使用清晰、统一的方式初始化所有对象:列表初始化。【C++】

不管是为了统一性&#xff0c;还是避免发生窄化转换&#xff0c;尽可能使用初始化列表。 说明哪些对象可以使用列表初始化&#xff1f;代码演示 说明 C11 引入了列表初始化&#xff08;也称为统一初始化或初始化列表&#xff09;&#xff0c;它是一种使用花括号 {} 来初始化对…

Linux网络编程: TCP协议之序号和确认号详解

一、TCP协议首部 二、序号&#xff08;Sequence Number&#xff09; 32位&#xff0c;表示该报文段所发送数据的第一个字节的编号。 实际上 TCP 的序号并不是按照 “一条两条” 这样的方式来编号的。在TCP连接中所传输字节流的每一个字节都会按顺序编号&#xff0c;由于序列号…

【物联网开源平台】tingsboard二次开发环境搭建+编译

文章目录 一&#xff0c;需要准备的环境二&#xff0c;获取tingsboard源码1.git拉取源码2.下载源码压缩包 三.新建仓库存放依赖文件四&#xff0c;编译五&#xff0c;遇到的错误 提示&#xff1a; 1.这篇只要准备两个环境&#xff0c;方法更简单&#xff01; 2.基于tingsboard …

谷歌seo营销服务有哪些服务?

以我们举例&#xff0c;如果你在做B2B外贸建站&#xff0c;这里有全套保姆式托管服务&#xff0c;让你既省心又省力&#xff0c;七天就能搞定网站建设&#xff0c;快速上线&#xff0c;再来就是谷歌白帽SEO&#xff0c;我们这边强调的是纯白帽操作&#xff0c;专注于高质量的原…

在Sequence中缓存Niagara粒子轨道

当Sequence中粒子特效较多时&#xff0c;播放检查起来较为麻烦&#xff0c;而使用Niagara缓存功能可将粒子特效方便的缓存起来&#xff0c;并且还可以更改播放速度与正反播放方向&#xff0c;便于修改。 1.使用Niagara缓存需要先在插件里打开NiagaraSimCaching 2.创建一个常…

Visual Studio - Platform Toolset

Visual Studio - Platform Toolset 1. Microsoft Visual Studio 2013 - Platform Toolset2. Microsoft Visual Studio 2015 - Platform ToolsetReferences 1. Microsoft Visual Studio 2013 - Platform Toolset (right mouse click on the project) -> 属性 -> 配置属性…