当前位置: 首页 > news >正文

Android Google Maps

Android 谷歌地图

  • 前言
  • 正文
    • 一、设置Google Cloud 项目
    • 二、项目配置
      • ① 设置SDK
      • ② 配置API密钥
      • ③ 配置AndroidManifest.xml
    • 三、添加地图
    • 四、定位当前
      • ① 请求定位权限
      • ② 我的位置控件
      • ③ 获取当前位置
    • 五、配置地图
      • ① xml配置地图
      • ② 代码配置地图
      • ③ 地图点击事件
      • ④ 管理Marker
    • 六、地址位置编码
      • ① 坐标转地址
      • ② 地址转坐标
    • 七、源码

前言

  在国内你选择的SDK可以是高德、百度、腾讯等,但在国外,你首选肯定是谷歌,因此要进行Google地图的开发你首先要解决下面三个问题

  • VPN
  • Google账号
  • 信用卡
    • American Express(美国运通卡)
    • Discover(美国发现卡)
    • JCB(Japan Credit Bureau,日本国际信用卡)
    • MasterCard(万事达)
    • VISA(维萨)

正文

  首先我们进入Google的地图开发平台,点击:Google Maps进入,建议你使用Google Chrome进行访问。

在这里插入图片描述

一、设置Google Cloud 项目

在这里插入图片描述

点击这里的创建新项目按钮。

在这里插入图片描述

输入名字后,点击创建。

在这里插入图片描述
  然后我们进入API和服务,然后你就会发现你需要设置账号信息和付款验证信息,这一步还挺麻烦的,主要是那个卡的信息,在前面我已经提过了。

  在你通过账号信息验证之后就可以创建API秘钥了,创建的API之后需要对应使用应用的包名和SHA1证书指纹,一个API秘钥可以增加多个App进行配置,只有配置之后的App才能通过此API秘钥访问Google Maps。
在这里插入图片描述
在你配置好之后你就会得到一个API密钥,这个密钥我们需要在项目中配置好,下面进入项目。

二、项目配置

  一般情况这里是要进入项目的创建和配置了,而因为Google这边比较特殊,你可能需要先上架一个应用上去,我这边的正式版的,你可以试试测试版行不行,有应用之后我们就可以通过选择应用,使配置的API密钥去生效。

在这里插入图片描述
  我之前在使用的时候就遇到过一个指纹不对的情况,结果发现你的应用有两个指纹,你可以理解为测试版和正式版,如果你遇到这个情况,那么你换一下试试看。

① 设置SDK

  首先你要检查一下你的项目是否导入google()mavenCentral()这两个仓库,如果没有的话你就需要导入了,有则不用管,而根据你所使用的Gradle的不同,你配置这两个仓库的地方也不一样,如果Gradle是7.4以上的则在settings.gradle文件中配置,否则在工程级build.gradle配置,我这边就是工程级build.gradle,如下所示:

repositories {google()mavenCentral()
}

  然后我们找到需要使用地图的模块,例如app模块,找到该模块下的build.gradle,在里面中dependencies{}闭包中添加如下依赖:

	// Maps SDK for Androidimplementation 'com.google.android.gms:play-services-maps:19.0.0'

  同时我们注意配置一下buildFeatures,在模块级 build.gradlebuildFeatures 部分中 或 build.gradle 文件中,请添加 BuildConfig 类,该类可用于 访问此过程后面部分定义的元数据值:

buildFeatures {buildConfig true
}

这里你可以先Sync Now同步一下,也可以不急,在配置了API密钥之后再同步。

② 配置API密钥

基于Google上推荐的配置方式,我们这里首先在打开工程的build.gradle,在里面添加

buildscript {dependencies {classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"}
}

这是Android 版 Secret Gradle 插件,然后打开app模块下build.gradle,在plugins{}闭包中添加如下代码:

	id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'

  然后我们将build.gradle文件中,设置 targetSdkcompileSdk 到 34,如果不能到34,那么你的相关的依赖就需要降低一些版本,否则会出现同步失败的情况,这是你可以Sync Now同步一下了。

  接着我们在功能的根目录下创建一个secrets.properties 文件,请注意它和你的工程级build.gradle是同级的,在这个文件里面配置如下代码:

MAPS_API_KEY=YOUR_API_KEY

  注意将YOUR_API_KEY,替换为你实际申请到的API密钥,然后保存文件,然后同样是这个目录,我们再创建一个local.defaults.properties文件,里面的代码如下所示:

MAPS_API_KEY=DEFAULT_API_KEY

  此文件的作用是为 API 密钥提供备用位置,以免在找不到 secrets.properties 文件的情况下构建失败。如果您是从省略 secrets.properties 的版本控制系统中克隆应用,而您还没有在本地创建 secrets.properties 文件来提供 API 密钥,就可能会出现构建失败。然后保存文件。

接着我们打开 AndroidManifest.xml 文件,在<application> 标签中添加如下代码:

<meta-dataandroid:name="com.google.android.geo.API_KEY"android:value="${MAPS_API_KEY}" />

最后我们在app模块下的android{}闭包中增加一个secrets属性,如果该属性不存在,代码如下所示:

secrets {propertiesFileName = "secrets.properties"defaultPropertiesFileName = "local.defaults.properties"ignoreList.add("keyToIgnore") ignoreList.add("sdk.*")       
}        

下面再Sync Now同步一下。

③ 配置AndroidManifest.xml

  首先配置Google Play 服务版本号,在 application标签中添加以下声明。该操作会嵌入编译应用时所用 Google Play 服务的版本,代码如下所示:

<meta-dataandroid:name="com.google.android.gms.version"android:value="@integer/google_play_services_version" />

然后再增加一个Apache HTTP 旧版库,代码如下所示:

<uses-libraryandroid:name="org.apache.http.legacy"android:required="false" />

最后我们再配置一下需要使用到的权限,

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

  到此为止配置工作就完成了,你可以先不急着添加地图,先运行一下试试看,有没有报错或者其他的问题,如果没有问题再进行下一步。

三、添加地图

首先我们在工程中创建一个map包,里面新建一个GoogleMapActivity。
在这里插入图片描述

完成创建之后,我们用上ViewBinding,代码如下所示:

class GoogleMapActivity : AppCompatActivity() {private lateinit var binding: ActivityGoogleMapBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()binding = ActivityGoogleMapBinding.inflate(layoutInflater)setContentView(binding.root)ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)insets}}
}

  注意自行导包,并且设置一个可以进入这个页面的入口,比如点击一个按钮跳转到这个页面来。下面我们配置XML,打开activity_google_map.xml,修改后代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:map="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"><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/map"android:name="com.google.android.gms.maps.SupportMapFragment"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

  这里我们以静态方式添加 fragment,在用于处理地图的 activity 的布局文件中,添加名称声明 xmlns:map="http://schemas.android.com/apk/res-auto"。完成此操作后即可使用 maps 自定义 XML 属性。在后面我们就可以直接在xml中通过map去设置地图的一些属性了。将 android:name 属性设置为com.google.android.gms.maps.SupportMapFragment,这是必须要做的事情。

接下来回到GoogleMapActivity,首先我们创建一个initView()函数,代码如下:

    /*** 初始化视图*/private fun initView() {val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragmentmapFragment.getMapAsync(this)}

  这里获取 fragment 的句柄并注册回调函数,GoogleMapActivity需要实现OnMapReadyCallback接口,重写里面的onMapReady()函数,在这个方法中我们添加一个Marker,代码如下所示:

    /*** 地图就绪*/override fun onMapReady(googleMap: GoogleMap) {googleMap.addMarker(MarkerOptions().position(LatLng(0.0, 0.0)).title("Marker"))}

最后要在onCreate()函数中调用initView()函数,最终代码如下图所示:

在这里插入图片描述

下面我们运行一下看看

在这里插入图片描述
  OK,你会看到出现了这个Marker,就是我们所设置的地方,如果你没有加载出这个画面,那么检查一下你的控制台,看看有没有相关的错误日志,再根据日志判断具体问题,一般都是配置的问题,请根据一、二步骤进行检查。

四、定位当前

  上述的内容对你毫无难度,我们继续往下走,现在地图加载出来了,我们最实际的想法就是定位当前所在位置,那么要怎么做呢,这里分为两种方式,无论那种方式,我们都需要先获取位置权限。

① 请求定位权限

  在Android6.0及以上版本定位权限光在AndroidManifest.xml配置还不够,还需要动态请求,下面我们在GoogleMapActivity中完成这一代码。

  首先声明变量,如下所示:

    private val TAG: String = GoogleMapActivity::class.java.simpleName// 权限请求码private val LOCATION_PERMISSION_REQUEST_CODE: Int = 9527// 地图private lateinit var map: GoogleMap

这里的map我们需要在onMapReady()函数中进行赋值,

    override fun onMapReady(googleMap: GoogleMap) {map = googleMap// 检查权限checkPermission()}

通过我们增加一个检查权限的函数,也就地图就绪之后我们就检查权限,代码如下所示:

	/*** 检查权限*/private fun checkPermission() {// 检查当前是否拥有精确位置或粗略位置权限if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {// 权限已授予,可以进行定位操作Log.d(TAG, "checkPermission: 权限已授予")configMap()} else {Log.d(TAG, "checkPermission: 请求权限")// 请求权限ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION),LOCATION_PERMISSION_REQUEST_CODE)}}

  这里的代码很常规就是,先检查有没有相关权限,有就配置地图,没有就请求权限。然后写一个配置地图的函数,代码如下:

    /*** 地图配置*/private fun configMap() {Log.d(TAG, "configMap: 地图配置")map.addMarker(MarkerOptions().position(LatLng(0.0, 0.0)).title("Marker"))}

最后重写onRequestPermissionsResult()函数,捕获权限请求结果,代码如下所示:

    /*** 权限请求结果*/override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)when (requestCode) {LOCATION_PERMISSION_REQUEST_CODE -> {// 如果请求被取消,则结果数组为空if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 权限被授予,可以进行定位操作Log.d(TAG, "onRequestPermissionsResult: 权限被授予")configMap()} else {// 权限被拒绝,无法进行定位操作Log.d(TAG, "onRequestPermissionsResult: 权限被拒绝")Toast.makeText(this, "拒绝将无法使用定位功能", Toast.LENGTH_SHORT).show()finish()}}}}

获取权限则配置地图,拒绝权限直接提示一下就退出了,下面我们运行一下:

在这里插入图片描述

再看看控制台日志:

在这里插入图片描述

OK,没有问题,现在权限的问题我们就解决了,下面进行定位。

② 我的位置控件

  要定位到当前位置,我们可以使用Google地图中的自带控件,修改configMap()函数,代码如下所示:

    /*** 地图配置*/@SuppressLint("MissingPermission")private fun configMap() {Log.d(TAG, "configMap: 地图配置")map.addMarker(MarkerOptions().position(LatLng(0.0, 0.0)).title("Marker"))map.isMyLocationEnabled = true// 当前位置图标的点击事件map.setOnMyLocationButtonClickListener {Log.d(TAG, "configMap: 点击位置图标")return@setOnMyLocationButtonClickListener false }// 定位后的蓝点点击事件map.setOnMyLocationClickListener { location ->Log.d(TAG, "configMap: 点击我的位置 $location")Toast.makeText(this, "Current location:\n$location", Toast.LENGTH_LONG).show()}}

  注意要加上这个@SuppressLint("MissingPermission"),不然会检查map.isMyLocationEnabled = true是否通过权限判断,这里我们在地图上启用“我的位置”图层。则地图上就会出现一个定位当前位置的控件,出现在右上角,setOnMyLocationButtonClickListener 则是这个控件的点击监听,这里返回false,则点击之后就会移动地图中心到当前设备所在位置,setOnMyLocationClickListener 则是定位后的蓝色点的点击事件,这里运行之后就会看到。

在这里插入图片描述
你会看到右上角的定位按钮,点击就可以了,控制台如下所示:

在这里插入图片描述

③ 获取当前位置

首先声明变量

    // 地图private lateinit var map: GoogleMap// Places API 的入口点。private lateinit var placesClient: PlacesClient// 融合位置信息提供程序的入口点。private lateinit var fusedLocationProviderClient: FusedLocationProviderClient// 最后已知位置private var lastKnownLocation: Location? = nullcompanion object {private val TAG = GoogleMapActivity::class.java.simpleName// 默认缩放private const val DEFAULT_ZOOM = 15// 权限请求码private const val LOCATION_PERMISSION_REQUEST_CODE = 9527// 未授予位置权限时使用的默认位置(澳大利亚悉尼)和默认缩放。private val defaultLocation = LatLng(-33.8523341, 151.2106085)}

initView()函数中增加如下代码:

        // 构造 PlacesClientPlaces.initialize(applicationContext, BuildConfig.MAPS_API_KEY)placesClient = Places.createClient(this)// 构造 FusedLocationProviderClient。fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

添加位置如下图所示:

在这里插入图片描述

下面我们需要写一个getCurrentLocation()函数获取当前位置,代码如下所示:

    /*** 获取当前位置*/@SuppressLint("MissingPermission")private fun getCurrentLocation() {Log.d(TAG, "getCurrentLocation: 获取当前位置")fusedLocationProviderClient.lastLocation.addOnCompleteListener { task ->// 获取当前位置未成功if (!task.isSuccessful) {Log.d(TAG, "Current location is null. Using defaults.")Log.e(TAG, "Exception: %s", task.exception)// 设置默认位置changeMapCenter(defaultLocation)return@addOnCompleteListener}lastKnownLocation = task.resultif (lastKnownLocation == null) return@addOnCompleteListener// 移动地图到当前位置changeMapCenter(LatLng(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude))}}

这里有一个changeMapCenter()函数,用于改变地图中心,代码如下所示:

    /*** 改变地图中心*/private fun changeMapCenter(latLng: LatLng) {map.addMarker(MarkerOptions().title("Marker").position(latLng))// 地图中移动到经纬度处map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM.toFloat()))}

  最后我们在configMap()函数中调用getCurrentLocation()函数,同时去掉之前的默认Marker,如下图所示:

在这里插入图片描述

  下面你运行一下就会自动定位到当前设备所在位置了,这个位置不定义完全正确,有一些偏差,控制台日志如下图所示:

在这里插入图片描述

五、配置地图

① xml配置地图

  完成定位之后,我们可以对地图进行一些配置,我们可以通过xml去配置。

		map:cameraTilt="30"map:uiRotateGestures="true"map:uiZoomControls="true" 
  • cameraTilt 将地图倾斜度设置为 30
  • uiRotateGestures启用旋转手势控件
  • uiZoomControls启用缩放控件

添加位置如下图所示:

在这里插入图片描述

XML属性还有其他的设置:

  • mapType - 要显示的地图类型。有效值包括:none、normal、hybrid、satellite 和 terrain。
  • cameraTargetLat、cameraTargetLng、cameraZoom、cameraBearing、cameraTilt - 镜头的初始位置。
  • uiZoomControls、uiCompass - 用于指定是否显示缩放控件和罗盘。
  • uiZoomGestures、uiScrollGestures、uiRotateGestures、uiTiltGestures - 用于指定是否启用特定手势。
  • zOrderOnTop - 用于指明地图视图的表面是否叠加显示在地图窗口、地图控件和窗口中的任何对象上。
  • useViewLifecycle - 此属性必须与 SupportMapFragment 对象一起使用才有效,它用于指定是否应将地图的生命周期与 fragment 的视图或 fragment 本身关联。
  • liteMode - 如果要启用精简模式,则为 true;否则为 false。

② 代码配置地图

xml可以设置的,同样可以通过代码设置。就需要用到GoogleMapOptionsUiSettings,如果你使用的是动态加载的地图,那么就使用GoogleMapOptions的方式,如果是静态加载的地图就使用UiSettings,这里我们使用UiSettings去设置地图,修改一下configMap()中的代码,如下图所示:

    /*** 地图配置*/@SuppressLint("MissingPermission")private fun configMap() {Log.d(TAG, "configMap: 地图配置")map.apply {isMyLocationEnabled = true // 地图上启用“我的位置”图层// 当前位置图标的点击事件setOnMyLocationButtonClickListener { Log.d(TAG, "configMap: 点击位置图标")return@setOnMyLocationButtonClickListener false}// 定位后的蓝点点击事件setOnMyLocationClickListener { location ->  Log.d(TAG, "configMap: 点击我的位置 $location")Toast.makeText(this@GoogleMapActivity, "Current location:\n$location", Toast.LENGTH_LONG).show()}// 地图设置uiSettings.apply {isZoomControlsEnabled = true // 显示缩放按钮isMyLocationButtonEnabled = true // 显示定位按钮isCompassEnabled = true // 显示指南针isMapToolbarEnabled = true // 显示地图工具栏isRotateGesturesEnabled = true // 允许旋转手势isScrollGesturesEnabled = true // 允许滚动手势isTiltGesturesEnabled = true // 允许倾斜手势isZoomGesturesEnabled = true // 允许缩放手势isScrollGesturesEnabledDuringRotateOrZoom = true // 允许在旋转或缩放时滚动手势isIndoorLevelPickerEnabled = true // 显示室内层选择器}}// 获取当前位置getCurrentLocation()}

主要是注意uiSettings里面的配置,可以自行运行看配置效果。

③ 地图点击事件

  关于地图的事件我们主要讲述点击事件,比如我们点击哪里就移动地图到哪里,这是很常用的一个功能,实现起来也很简单,在configMap()函数中添加如下代码:

			// 地图点击事件setOnMapClickListener { latLng ->changeMapCenter(latLng)}

这里就做到了,点击哪里移动到哪里,因为在changeMapCenter()函数中,对于定位点进行添加Marker,所以,如果你点击了地图很多次,那么可能每一次都会绘制一个Marker,有时候你就不知道当前到底在哪里,那么为了解决这个问题,可以只保留一个Marker。

④ 管理Marker

首先我们声明一个变量

    // 标记private var marker: Marker? = null

然后修改changeMapCenter()函数的代码,如下所示:

    private fun changeMapCenter(latLng: LatLng) {// 移除标点marker?.remove()// 添加标点marker = map.addMarker(MarkerOptions().title("Marker").position(latLng))// 地图中移动到经纬度处map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM.toFloat()))}

这里在赋值之前先移除,如果不为空就会移除再添加到,另外我们还可以在点击当前位置按钮的时候移除,代码如下所示:

			setOnMyLocationButtonClickListener {Log.d(TAG, "configMap: 点击位置图标")// 移除标点marker?.remove()marker = nullreturn@setOnMyLocationButtonClickListener false}

这样Marker就是唯一的一个,我们还可以修改Marker的样式。通过MarkerOptions进行设置,比如icon(图标),alpha(透明度)。

		marker = map.addMarker(MarkerOptions().title("Marker") // 设置标题.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW)) // 设置默认图标颜色为黄色.alpha(0.7f) // 设置透明度.position(latLng) // 设置位置)

类似这样修改,当然icon还可以设置自定义的图标,比如这样:

.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_google))

这个图标可以设置成自己需要的图标。

六、地址位置编码

  地址位置编码分为两种情况,通过经纬度获取详细地址,通过地址获取经纬度坐标。无论那种方式,在国内都有限制。

① 坐标转地址

首先我们来写坐标转地址,地址的结果我们通过Address来接收,这是一个列表,首先我们声明变量:

    // 地理编码器private var geocoder: Geocoder? = null// 地址结果private var addressesLiveData: MutableLiveData<List<Address>> = MutableLiveData()

然后在configMap()函数中增加如下代码:

        // 初始化地理编码器geocoder = Geocoder(this)// 编码结果addressesLiveData.observe(this) { addresses ->// 获取地址信息if (!addresses.isNullOrEmpty()) {val address = addresses[0]Log.d(TAG, "Address: ${address.latitude} ${address.longitude} ${address.countryName} ${address.adminArea} ${address.locality} ${address.thoroughfare} ${address.subThoroughfare}")}}

在观察到数据改变时,打印出来。当前的Activity需要实现接口

在这里插入图片描述
主要加上这个注解,然后重写onGeocode()函数,代码如下所示:

    /*** 地理编码结果,经纬度坐标转地址*/override fun onGeocode(addresses: MutableList<Address>) {addressesLiveData.postValue(addresses)}

然后再增加一个getDetailAddress()函数

    /*** 获取详情位置信息,获取国内位置会出现异常*/private fun getDetailAddress(latLng: LatLng) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {geocoder?.getFromLocation(latLng.latitude, latLng.longitude, 1, this@GoogleMapActivity)} else {addressesLiveData.postValue(geocoder?.getFromLocation(latLng.latitude, latLng.longitude, 1))}}

  在这里我们通过geocoder去获取详细的地址信息,这里就需要进行版本的判断了,1表示返回的最大结果数,可以自行修改。最后在changeMapCenter()函数中调用getDetailAddress()函数,如下图所示:

在这里插入图片描述
运行后,控制台日志如下图所示:

在这里插入图片描述

② 地址转坐标

这里我们只需要写一个getDetailLatLng()函数就可以了,代码如下所示:

    /*** 获取默认经纬度的地址信息*/private fun getDetailLatLng(address: String = "悉尼歌剧院") {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {geocoder?.getFromLocationName(address, 1, this@GoogleMapActivity)} else {addressesLiveData.postValue(geocoder?.getFromLocationName(address, 1))}}

这里我们使用的是默认值悉尼歌剧院,看是否能够通过地址名称获取具体的地址信息,这里的接口是一样的,因此我们在使用的使用要么只用一个,要么通过一个变量来判断是坐标转地址还是地址转坐标。这里我只使用一个。

在这里插入图片描述

运行看看效果:

在这里插入图片描述
好的,这样就完成了,通过这个获取到的数据还不是最准确的,通过Google API接口去获取比较准备,感兴趣的可以去看看。

七、源码

   因为涉及到项目,所以这里我就不贴源码,只贴上GoogleMapActivity的完整代码:

@SuppressLint("NewApi")
class GoogleMapActivity : AppCompatActivity(), OnMapReadyCallback, Geocoder.GeocodeListener {private lateinit var binding: ActivityGoogleMapBinding// 地图private lateinit var map: GoogleMap// Places API 的入口点。private lateinit var placesClient: PlacesClient// 融合位置信息提供程序的入口点。private lateinit var fusedLocationProviderClient: FusedLocationProviderClient// 最后已知位置private var lastKnownLocation: Location? = null// 标记private var marker: Marker? = null// 地理编码器private var geocoder: Geocoder? = null// 地址结果private var addressesLiveData: MutableLiveData<List<Address>> = MutableLiveData()companion object {private val TAG = GoogleMapActivity::class.java.simpleName// 默认缩放private const val DEFAULT_ZOOM = 15// 权限请求码private const val LOCATION_PERMISSION_REQUEST_CODE = 9527// 未授予位置权限时使用的默认位置(澳大利亚悉尼)和默认缩放。private val defaultLocation = LatLng(-33.8523341, 151.2106085)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()binding = ActivityGoogleMapBinding.inflate(layoutInflater)setContentView(binding.root)ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)insets}initView()}/*** 初始化视图*/private fun initView() {// 构造 PlacesClientPlaces.initialize(applicationContext, BuildConfig.MAPS_API_KEY)placesClient = Places.createClient(this)// 构造 FusedLocationProviderClient。fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragmentmapFragment.getMapAsync(this)}/*** 检查权限*/private fun checkPermission() {// 检查当前是否拥有精确位置或粗略位置权限if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {// 权限已授予,可以进行定位操作Log.d(TAG, "checkPermission: 权限已授予")configMap()} else {Log.d(TAG, "checkPermission: 请求权限")// 请求权限ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION),LOCATION_PERMISSION_REQUEST_CODE)}}/*** 地图就绪*/override fun onMapReady(googleMap: GoogleMap) {map = googleMap// 检查权限checkPermission()}/*** 地图配置*/@SuppressLint("MissingPermission")private fun configMap() {Log.d(TAG, "configMap: 地图配置")// 初始化地理编码器geocoder = Geocoder(this)// 编码结果addressesLiveData.observe(this) { addresses ->// 获取地址信息if (!addresses.isNullOrEmpty()) {val address = addresses[0]Log.d(TAG, "Address: ${address.latitude} ${address.longitude} ${address.countryName} ${address.adminArea} ${address.locality} ${address.thoroughfare} ${address.subThoroughfare}")}}map.apply {isMyLocationEnabled = true // 地图上启用“我的位置”图层// 当前位置图标的点击事件setOnMyLocationButtonClickListener {Log.d(TAG, "configMap: 点击位置图标")// 移除标点marker?.remove()marker = nullreturn@setOnMyLocationButtonClickListener false}// 定位后的蓝点点击事件setOnMyLocationClickListener { location ->Log.d(TAG, "configMap: 点击我的位置 $location")Toast.makeText(this@GoogleMapActivity, "Current location:\n$location", Toast.LENGTH_LONG).show()}// 地图点击事件setOnMapClickListener { latLng ->changeMapCenter(latLng)}// 地图设置uiSettings.apply {isZoomControlsEnabled = true // 显示缩放按钮isMyLocationButtonEnabled = true // 显示定位按钮isCompassEnabled = true // 显示指南针isMapToolbarEnabled = true // 显示地图工具栏isRotateGesturesEnabled = true // 允许旋转手势isScrollGesturesEnabled = true // 允许滚动手势isTiltGesturesEnabled = true // 允许倾斜手势isZoomGesturesEnabled = true // 允许缩放手势isScrollGesturesEnabledDuringRotateOrZoom = true // 允许在旋转或缩放时滚动手势isIndoorLevelPickerEnabled = true // 显示室内层选择器}}// 获取当前位置getCurrentLocation()}/*** 获取当前位置*/@SuppressLint("MissingPermission")private fun getCurrentLocation() {Log.d(TAG, "getCurrentLocation: 获取当前位置")fusedLocationProviderClient.lastLocation.addOnCompleteListener { task ->// 获取当前位置未成功if (!task.isSuccessful) {Log.d(TAG, "Current location is null. Using defaults.")Log.e(TAG, "Exception: %s", task.exception)// 设置默认位置changeMapCenter(defaultLocation)return@addOnCompleteListener}lastKnownLocation = task.resultif (lastKnownLocation == null) return@addOnCompleteListener// 移动地图到当前位置changeMapCenter(LatLng(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude))}}/*** 改变地图中心*/private fun changeMapCenter(latLng: LatLng) {// 移除标点marker?.remove()// 添加标点marker = map.addMarker(MarkerOptions().title("Marker") // 设置标题.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_google)) // 设置自定义图标.alpha(0.7f) // 设置透明度.position(latLng) // 设置位置)// 获取详细位置信息// getDetailAddress(latLng)// 获取默认经纬度的地址信息getDetailLatLng()// 地图中移动到经纬度处map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM.toFloat()))}/*** 获取默认经纬度的地址信息*/private fun getDetailLatLng(address: String = "悉尼歌剧院") {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {geocoder?.getFromLocationName(address, 1, this@GoogleMapActivity)} else {addressesLiveData.postValue(geocoder?.getFromLocationName(address, 1))}}/*** 获取详情位置信息,获取国内位置会出现异常*/private fun getDetailAddress(latLng: LatLng) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {geocoder?.getFromLocation(latLng.latitude, latLng.longitude, 1, this@GoogleMapActivity)} else {addressesLiveData.postValue(geocoder?.getFromLocation(latLng.latitude, latLng.longitude, 1))}}/*** 权限请求结果*/override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)when (requestCode) {LOCATION_PERMISSION_REQUEST_CODE -> {// 如果请求被取消,则结果数组为空if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 权限被授予,可以进行定位操作Log.d(TAG, "onRequestPermissionsResult: 权限被授予")configMap()} else {// 权限被拒绝,无法进行定位操作Log.d(TAG, "onRequestPermissionsResult: 权限被拒绝")Toast.makeText(this, "拒绝将无法使用定位功能", Toast.LENGTH_SHORT).show()finish()}}}}/*** 地理编码结果,经纬度坐标转地址*/override fun onGeocode(addresses: MutableList<Address>) {addressesLiveData.postValue(addresses)}
}

该导包的地方注意导包即可。

相关文章:

Android Google Maps

Android 谷歌地图 前言正文一、设置Google Cloud 项目二、项目配置① 设置SDK② 配置API密钥③ 配置AndroidManifest.xml 三、添加地图四、定位当前① 请求定位权限② 我的位置控件③ 获取当前位置 五、配置地图① xml配置地图② 代码配置地图③ 地图点击事件④ 管理Marker 六、…...

Linux——进程概念

什么是操作系统 操作系统管理各种计算机硬件、为应用程序提供基础、并且充当计算机硬件与用户之间的中介。 冯诺依曼体系 这里的存储器指的是内存不考虑缓存情况&#xff0c;这里的CPU能且只能对内存进行读写&#xff0c;不能访问外设(输入或输出设备)外设(输入或输出设备)要…...

【H2O2|全栈】关于HTML(1)认识HTML

HTML相关知识 目录 前言 准备工作 WEB前端是什么&#xff1f; HTML是什么&#xff1f; 如何运行HTML文件&#xff1f; 标签 概念 分类 双标签和单标签 行内标签和块标签 HTML文档结构 预告和回顾 UI设计相关 Markdown | Md文档相关 项目合作管理相关 后话 前…...

Oracle(111) 如何使用RMAN备份数据库?

使用 RMAN&#xff08;Recovery Manager&#xff09;备份 Oracle 数据库是确保数据安全和可恢复性的关键步骤。下面是详细的指导和代码示例&#xff0c;展示如何使用 RMAN 进行数据库备份。 1. 准备工作 在开始备份之前&#xff0c;需要确保以下几点&#xff1a; 已安装并配…...

linux字符设备驱动程序

字符设备驱动程序简介  linux系统中万物皆文件&#xff0c;驱动程序加载后会在/dev目录下生成一 个对应的文件&#xff0c;如/dev/led。应用程序就是先用open打开该文件&#xff0c; 用write控制led的亮灭&#xff0c;用read读取led的亮灭&#xff0c;用完之后用close 关闭该…...

【pyhton】python如何实现将word等文档中的文字转换成语音

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

Claude Enterprise推出计划

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

【前端】CSS控制style样式失效

在CSS中&#xff0c;可以通过几种方式控制或禁用特定的style样式。 使用all: unset来重置所有可继承的属性&#xff0c;并清除所有的样式&#xff1a; .element {all: unset;} 使用inherit值来使属性获取其父元素的值&#xff1a; .element {color: inherit;font-size: inh…...

How can I load the openai api configuration through js in html?

题意&#xff1a;怎样在HTML中通过JavaScript加载OpenAI API配置 问题背景&#xff1a; I am trying to send a request through js in my html so that openai analyzes it and sends a response, but if in the js I put the following: 我正在尝试通过HTML中的JavaScript发…...

Pipeline流水线通过git拉取Jenkinsfile报错 error: RPC failed; result=22, HTTP code = 404

Pipeline流水线通过git拉取Jenkinsfile报错 error: RPC failed; result22, HTTP code 404 在学习共享库时使用通过git拉取jenkinsfile时&#xff0c;报错在排查gitlab服务状态&#xff0c;网络通讯&#xff0c;防火墙规则以及Jenkins凭据均可以正常使用&#xff0c;最后发现的…...

【与C++的邂逅】--- string容器使用

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 与C的邂逅 本篇博客我们将来了解string容器本身以及接口的使用。 string是串&#xff0c;本质是一个字符数组&#xff0c;可以对其进行增删查改。 &am…...

1-18 平滑处理——高斯滤波 opencv树莓派4B 入门系列笔记

目录 一、提前准备 二、代码详解 cv2.GaussianBlur函数用于对图像进行高斯滤波。高斯滤波是一种平滑图像的技术&#xff0c;用于减少噪声和细节。函数的三个参数如下&#xff1a; 三、运行结果 四、完整工程贴出 一、提前准备 1、树莓派4B 及 64位系统 2、提前安装opencv库…...

小爱打工,你躺平!让「微信AI小助理」接管你的文件处理,一个字:爽!

前两天&#xff0c;搞了个微信 AI 小助理-小爱(AI)&#xff0c;爸妈玩的不亦乐乎。 零风险&#xff01;零费用&#xff01;我把AI接入微信群&#xff0c;爸妈玩嗨了&#xff0c;附教程&#xff08;下&#xff09; 最近一直在迭代中&#xff0c;挖掘小爱的无限潜力: 链接丢给…...

管理学习(一)马云《赢在中国》创业演讲整理

目录 一、小公司也需要制度二、不要害怕冒险三、创业者要的不是技术&#xff0c;而是胆识四、不要惧怕和大企业竞争五、理念不一样&#xff0c;老板永远是对的六、要真实地为客户创造价值七、跟风险投资谈判&#xff0c;说到要做到八、风险投资&#xff0c;只能帮你不能救你九、…...

Opencv中的直方图(2)计算图像的直方图函数calcHist()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算一组数组的直方图。 函数 cv::calcHist 计算一个或多个数组的直方图。用于递增直方图bin的元组的元素是从相同位置的相应输入数组中获取的。…...

Buzzer:一款针对eBPF的安全检测与模糊测试工具

关于Buzzer Buzzer是一款功能强大的模糊测试工具链&#xff0c;该工具基于Go语言开发&#xff0c;可以帮助广大研究人员简单高效地开发针对eBPF的模糊测试策略。 功能介绍 下面给出的是当前版本的Buzzer整体架构&#xff1a; 元素解析&#xff1a; 1、ControlUnit&#xff1a…...

若依框架登录鉴权详解(动态路由)

若依框架登录鉴权&#xff1a;1.获取token&#xff08;过期在响应拦截器中实现&#xff09;,2.基于RBAC模型获取用户、角色和权限信息&#xff08;在路由前置守卫&#xff09;&#xff0c;3.根据用户权限动态生成&#xff08;从字符串->组件&#xff0c;根据permission添加动…...

孤儿进程、僵尸进程、守护进程(精灵进程)

目录 一、孤儿进程 二、僵尸进程 三、守护进程&#xff08;精灵进程&#xff09; 一、孤儿进程 定义&#xff1a;孤儿进程是指那些其父进程已经结束&#xff0c;但它们依然在运行的进程 创建一个孤儿进程&#xff1a; #include <stdio.h> #include <stdlib.h> #in…...

Centos9 网卡配置文件

1、Centos stream 9 网络介结 Centos以前版本&#xff0c;NetworkManage以ifcfg格式存储网络配置文件在/etc/sysconfig/networkscripts/目录中。但是&#xff0c;Centos steam 9现已弃用ifcfg格式&#xff0c;默认情况下&#xff0c;NetworkManage不再创建此格式的新配置文件。…...

ios免签H5

1、windows下载mobileconfig文件制作工具&#xff0c;可在csdn搜索iPhone_Mobileconfig_Tool下载安装&#xff1b;IOS 从APP Store 下载Apple Configurator 2 2、用申请的域名SSL证书给mobieconfig文件签名&#xff0c;最好下载Apache证书&#xff0c;里面包含 AE86211.crt…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...