跳到正文 Google Map Lab · Jimmy Huang's Blog
使用 Circle 画出某个地点周围 1km 半径范围
资料来源:本次上传的 FIT5046 Week 10 Google Map Lab PDF。:contentReferenceoaicite:0
Goal 如何申请并使用 Google Maps API key
Android 中 AndroidManifest.xml 的 permission 和 metadata 作用
如何在 Jetpack Compose 中显示 Google Map
如何使用 LatLng 表示地图坐标
如何使用 Marker 标记一个地点
如何请求 location permission
如何获取用户当前位置并移动 camera
如何使用 Polyline 连接多个点
如何使用 SphericalUtil 计算路线距离
如何使用 Circle 表示某个地点周围的范围
Task 0: 创建 Google Maps API Key 这个 Task 主要完成 Google Cloud 端的准备工作。
使用个人 Gmail 登录 Google Cloud Console
创建或选择一个 Google Cloud Project
启用 Maps SDK for Android
创建一个 API key
复制 API key,后面会放进 Android 项目里
Step 0.1:使用个人 Gmail 登录 Google Cloud Console Google Maps 服务不是 Android Studio 本地自带的,而是 Google Cloud 提供的服务。
Android App 要访问 Google Maps,需要一个 Google Cloud 项目和对应的 API key。
截图占位:这里放 Google Cloud Console 登录后的首页截图
Step 0.2:创建或选择 Google Cloud Project 在 Google Cloud Console 顶部导航栏选择 Project。
Google Cloud 里的 API、billing、credentials 都是绑定到 Project 上的。
Step 0.3:启用 Maps SDK for Android 你的 Android App 使用的是 Maps SDK for Android 。
如果这个 API 没有启用,就算你有 API key,地图也可能加载失败。
Step 0.4:创建 API Key API key 相当于你的 App 调用 Google Maps 服务的“通行证”。
Android App 运行时会通过这个 key 证明自己有权限使用 Google Maps。
Step 0.5:建议限制 API Key 如果需要填写 Android package name,可以在你的项目中查看:
如果需要 SHA-1,可以在 Android Studio 里运行 Gradle task:
限制 API key 可以减少 key 泄露后的风险。
例如别人拿到你的 key,也不能随便在其他 App 或其他 API 上使用。
Task 1: 配置 Android 项目 这个 Task 主要配置 Android 项目,让 App 能够使用 Google Maps。
在 AndroidManifest.xml 添加 location 权限
在 AndroidManifest.xml 添加 Google Maps API key
在 libs.versions.toml 添加 Google Maps 相关依赖
在 app-level build.gradle.kts 添加 implementation
Sync Gradle
Task 1.1:打开 Android 项目中的 Manifest 文件 在 <manifest> 标签下面加入 permission。
ACCESS_FINE_LOCATION:允许 App 获取较精确位置
ACCESS_COARSE_LOCATION:允许 App 获取大概位置
INTERNET:地图瓦片和 Google 服务需要网络
ACCESS_NETWORK_STATE:让 App 能检查网络状态
meta-data:把 Google Maps API key 提供给 Maps SDK
Google Map 不是一个普通 Compose UI 组件,它背后需要 Google Maps SDK。
SDK 启动时会从 Manifest 读取 API key,然后向 Google 服务请求地图数据。
Step 1.2:在 libs.versions.toml 添加版本号 如果你的文件里已经有 [versions],不要重复写标题,只添加缺少的行。
如果你当前项目的 Kotlin / Compose 版本比较旧,同步失败,可以尝试 Lab 给出的旧版本组合:
libs.versions.toml 是 Gradle Version Catalog。
它把依赖版本集中管理,后面在 build.gradle.kts 里就可以写:
而不需要每次都手写完整 dependency string。
Step 1.3:在 libs.versions.toml 添加 libraries alias 继续在同一个文件中找到 [libraries] 区域。
如果已经有 [libraries],不要重复写标题,只添加下面几行:
真正的 library 是 com.google.maps.android:maps-compose
版本号引用 [versions] 里的 mapsCompose
在 Gradle 里可以通过 libs.maps.compose 使用它
Step 1.4:在 app-level Gradle 添加 dependencies 在 dependencies { ... } 里面添加:
play-services-maps:Google Maps Android 基础 SDK
play-services-location:获取用户当前位置需要用
maps-compose:在 Jetpack Compose 中显示 Google Map
android-maps-utils:后面计算路线距离会用到 SphericalUtil
Step 1.5:Sync Gradle 点击 Android Studio 右上角或提示条中的:
Gradle Sync 成功
没有 dependency not found
没有 version conflict
没有 unresolved reference
Task 2: 显示基础 Google Map 和 Marker 这个 Task 是 Google Map 的最小可运行版本。
在 MainActivity.kt 创建一个 MapScreen
用 GoogleMap 显示地图
用 CameraPosition 设置地图初始位置
用 Marker 标记 Monash University Clayton
运行 App 检查地图是否加载成功
Step 2.1:打开 MainActivity.kt下面代码里的 package 要换成你自己项目的 package。
Step 2.2:写入基础地图代码 LatLng(-37.8770, 145.0443) 表示 Monash Clayton 的经纬度
CameraPosition.fromLatLngZoom(location, 15f) 表示地图初始镜头位置和缩放级别
GoogleMap(...) 是真正显示地图的 Compose 组件
Marker(...) 是地图上的红色标记点
如果你不设置 camera,地图不知道一开始应该显示哪里。
如果你不添加 marker,地图上不会标出目标地点。
Step 2.3:运行 App App 成功启动
页面顶部显示 Monash University
屏幕中显示 Google Map
地图中心附近有一个 marker
Task 3: 加入当前位置权限、My Location 蓝点、点击 Marker 和 Clear 这一部分比 Task 2 更接近真实地图 App 的逻辑。
Step 3.1:确认 Manifest 已经有定位权限 确认 AndroidManifest.xml 中已经有:
Manifest permission 是“声明 App 可能需要这个权限”。
但从 Android 6.0 开始,敏感权限还需要在运行时向用户弹窗申请。
Step 3.2:替换 MainActivity.kt 为完整进阶版本 基础地图
当前位置 permission request
当前定位
My Location 蓝点
点击地图放 marker
清除 marker
Step 3.3:运行 App 并允许定位权限 运行 App 后,系统应该弹出 permission dialog。
截图占位:这里放 App 请求 location permission 的截图
Step 3.4:如果当前位置获取不到,设置模拟器位置 如果你用的是 Emulator,当前位置可能为空。
可以在 Android Emulator 右侧工具栏打开:
输入一个位置,例如 Monash Clayton:
如果你不手动给模拟器设置位置, getCurrentLocation() 可能拿不到有效 location。
Task 4: 使用 Polyline 连接多个地点并计算总距离 这个 Task 对应 Google Maps 的 additional features。
Step 4.1:确认已经添加 maps utils dependency 确认 app/build.gradle.kts 中有:
确认 libs.versions.toml 中有:
SphericalUtil 不在基础 Maps SDK 里,而是在 Android Maps Utils library 里。
如果没有添加这个 dependency,后面会出现 Unresolved reference: SphericalUtil。
Step 4.2:把 MainActivity.kt 改成 RouteMapScreen 版本 可以在同一个 MainActivity.kt 中使用下面代码。
Step 4.3:运行 App
Step 4.4:理解距离计算逻辑 从第 1 个点开始
计算第 1 个点到第 2 个点的距离
再计算第 2 个点到第 3 个点的距离
一直加到最后
最后把 meters 转成 kilometers
SphericalUtil.computeDistanceBetween() 会按照地球球面距离计算,更适合地图坐标。
Task 5: 使用 Circle 显示地点周围 1km 范围 这个 Task 继续使用 Google Maps 的 additional features。
Step 5.1:把 MainActivity.kt 改成 Circle 版本
Step 5.2:运行 App
Step 5.3:理解 Circle 参数 Circle 常用于显示某个地点周围的覆盖范围,例如:
校园 1km 范围
餐厅配送范围
附近可搜索范围
用户当前位置周围区域
Task 6: 最终检查清单
Step 6.1:检查 Google Cloud 设置 已经创建 / 选择 Google Cloud Project
已经启用 Maps SDK for Android
已经创建 API key
API key 没有复制错误
如果限制了 key,package name / SHA-1 没填错
截图占位:这里放 Maps SDK for Android enabled 页面截图
Step 6.2:检查 AndroidManifest.xml 截图占位:这里放 Manifest 配置截图,真实 API key 请遮挡
Step 6.3:检查 Gradle dependencies 确认 app/build.gradle.kts 里有:
截图占位:这里放 Gradle dependencies 配置截图
本周必须理解的知识点
1. API Key 是什么 API key 是 Android App 使用 Google Maps 服务的凭证。
2. Manifest permission vs Runtime permission AndroidManifest.xml 里的 permission 只是声明:
3. LatLng 是什么
4. CameraPosition 是什么 CameraPosition 决定地图一开始看哪里、放大多少。
5. Marker 是什么
6. Polyline 是什么
7. Circle 是什么 某地点周围 1km 范围
搜索半径
配送范围
安全范围
附近推荐范围
关键概念对比
GoogleMap vs CameraPosition vs Marker
Permission 声明 vs Permission 请求
Marker vs Polyline vs Circle
play-services-maps vs maps-compose
老师可能会问什么
1. 你为什么要在 Manifest 里放 API key? 因为 Google Maps SDK 启动时需要读取 API key。
API key 告诉 Google Cloud:这个 App 有权限使用 Maps SDK for Android。
2. 为什么 Manifest 写了 location permission,代码里还要 request permission? Manifest 只是声明,runtime request 才是真正向用户申请。
3. rememberCameraPositionState 是做什么的? 它用来保存和控制 Google Map 的 camera 状态。
例如地图初始位置、缩放级别,以及后续移动 camera。
4. LatLng(-37.8770, 145.0443) 中两个数字分别是什么? 第一个是 latitude,第二个是 longitude。
5. 为什么要用 selectedLocation 作为 state? Compose 中 UI 应该根据 state 自动更新。
当用户点击地图时, selectedLocation 改变,marker 会重新显示到新位置。
6. 为什么 Polyline 需要一个 list of LatLng? Polyline 会按照 list 顺序把这些点连起来。
7. 为什么 Circle 的 radius 用 meters? Google Maps SDK 的 Circle 半径单位是 meters。
所以 1000.0 表示 1000 meters,也就是 1 km。
Summary 本周 Lab 的核心不是只让你“把地图跑出来”,而是让你理解 Android App 使用 Google Maps 的完整流程。
Google Cloud 端启用 Maps SDK for Android
创建 API key
Android Manifest 中配置权限和 API key
Gradle 中引入 Maps / Location / Compose / Utils dependencies
用 GoogleMap 在 Compose 中显示地图
用 LatLng 和 CameraPosition 决定地图显示位置
用 Marker 标记地点
用 runtime permission 获取用户授权
用 Fused Location Provider 获取当前位置
用 MapProperties 显示 My Location 蓝点
用 Polyline 画路线
用 SphericalUtil 计算路径距离
用 Circle 显示某个地点周围范围
只要这四层中有一层配置错,地图就可能无法显示或功能无法正常工作。
https://console.cloud.google.com/APIs & Services > LibraryAPIs & Services > Credentials+ CREATE CREDENTIALS > API keyAIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXApplication restrictions: Android apps
API restrictions: Maps SDK for Androidapp/src/main/AndroidManifest.xmlcom.example.week10googlemapapp/src/main/AndroidManifest.xmlgradle/libs.versions.toml[versions]
mapsCompose = "6.10.0"
playServicesLocation = "21.3.0"
playServicesMaps = "19.2.0"
mapsUtils = "3.10.0"[versions]
mapsCompose = "4.3.3"
playServicesLocation = "21.3.0"
playServicesMaps = "18.2.0"
mapsUtils = "3.9.0"[libraries]
play-services-maps = {group = "com.google.android.gms",name = "play-services-maps",version.ref = "playServicesMaps" }
play-services-location = {group = "com.google.android.gms",name = "play-services-location",version.ref = "playServicesLocation" }
maps-compose = {module = "com.google.maps.android:maps-compose",version.ref = "mapsCompose" }
maps-utils = {module = "com.google.maps.android:android-maps-utils",version.ref = "mapsUtils" }maps-compose = {module = "com.google.maps.android:maps-compose",version.ref = "mapsCompose" }app/src/main/java/你的包名/MainActivity.ktExtended Controls > LocationLatitude: -37.8770
Longitude: 145.0443maps-utils = {module = "com.google.maps.android:android-maps-utils",version.ref = "mapsUtils" }Google Cloud API key
↓
Android Manifest permissions + metadata
↓
Gradle dependencies
↓
Compose GoogleMap UI + marker / route / location logic< manifest xmlns:android= " http://schemas.android.com/apk/res/android " > < uses-permission android:name= " android.permission.INTERNET " /> < uses-permission android:name= " android.permission.ACCESS_NETWORK_STATE " /> < uses-permission android:name= " android.permission.ACCESS_COARSE_LOCATION " /> < uses-permission android:name= " android.permission.ACCESS_FINE_LOCATION " /> < application android:allowBackup= " true " android:icon= " @mipmap/ic_launcher " android:label= " @string/app_name " android:roundIcon= " @mipmap/ic_launcher_round " android:supportsRtl= " true " android:theme= " @style/Theme.YourApp " > < meta-data android:name= " com.google.android.geo.API_KEY " android:value= " PLACE_YOUR_API_KEY_HERE " /> < activity android:name= " .MainActivity " android:exported= " true " android:theme= " @style/Theme.YourApp " > < intent-filter > < action android:name= " android.intent.action.MAIN " /> < category android:name= " android.intent.category.LAUNCHER " /> </ intent-filter > </ activity > </ application > </ manifest > android:value="PLACE_YOUR_API_KEY_HERE" android:value="你的 Google Maps API key" implementation(libs.maps.compose) implementation(libs.play.services.maps) implementation(libs.play.services.location) implementation(libs.maps.compose) implementation(libs.maps.utils) package com.example.week10googlemap import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.Marker import com.google.maps.android.compose.MarkerState import com.google.maps.android.compose.rememberCameraPositionState class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val monashLocation = LatLng(-37.8770, 145.0443) setContent { MaterialTheme { MapScreen( location = monashLocation, title = "Monash University" ) } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun MapScreen( location: LatLng, title: String ) { val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(location, 15f) } Scaffold( topBar = { TopAppBar( title = { Text(title) } ) } ) { innerPadding -> GoogleMap( modifier = Modifier .fillMaxSize() .padding(innerPadding), cameraPositionState = cameraPositionState ) { Marker( state = MarkerState(position = location), title = title ) } } } < uses-permission android:name= " android.permission.ACCESS_COARSE_LOCATION " /> < uses-permission android:name= " android.permission.ACCESS_FINE_LOCATION " /> package com.example.week10googlemap import android.Manifest import android.annotation.SuppressLint import android.content.pm.PackageManager import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.compose.ui.platform.LocalContext import com.google.android.gms.location.LocationServices import com.google.android.gms.location.Priority import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.MapProperties import com.google.maps.android.compose.MapUiSettings import com.google.maps.android.compose.Marker import com.google.maps.android.compose.MarkerState import com.google.maps.android.compose.rememberCameraPositionState import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { InteractiveLocationMapScreen() } } } } @SuppressLint( "MissingPermission" ) @OptIn(ExperimentalMaterial3Api::class) @Composable fun InteractiveLocationMapScreen( title: String = "My Google Map" ) { val context = LocalContext.current val scope = rememberCoroutineScope() val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) } var hasLocationPermission by remember { mutableStateOf( ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED ) } var selectedLocation by remember { mutableStateOf<LatLng?>(null) } var statusText by remember { mutableStateOf( "Waiting for location permission..." ) } val defaultLocation = LatLng(-37.8770, 145.0443) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(defaultLocation, 12f) } val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission() ) { granted -> hasLocationPermission = granted statusText = if (granted) { "Permission granted. Fetching current location..." } else { "Location permission denied. You can still tap the map to place a marker." } } LaunchedEffect(Unit) { if (!hasLocationPermission) { permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } else { statusText = "Fetching current location..." } } LaunchedEffect(hasLocationPermission) { if (hasLocationPermission) { fusedLocationClient .getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null) .addOnSuccessListener { location -> if (location != null) { val currentLatLng = LatLng(location.latitude, location.longitude) selectedLocation = currentLatLng statusText = "Current location found." scope.launch { cameraPositionState.animate( CameraUpdateFactory.newLatLngZoom(currentLatLng, 16f) ) } } else { statusText = "Could not get current location. Try setting emulator location or tap the map." } } .addOnFailureListener { exception -> statusText = "Failed to get location: ${ exception.message } " } } } Scaffold( topBar = { TopAppBar( title = { Text(title) } ) } ) { innerPadding -> Box( modifier = Modifier .fillMaxSize() .padding(innerPadding) ) { GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState, properties = MapProperties( isMyLocationEnabled = hasLocationPermission ), uiSettings = MapUiSettings( myLocationButtonEnabled = hasLocationPermission, zoomControlsEnabled = true ), onMapClick = { tappedLocation -> selectedLocation = tappedLocation statusText = "Marker placed at: %.5f, %.5f" .format( tappedLocation.latitude, tappedLocation.longitude ) } ) { selectedLocation?.let { location -> Marker( state = MarkerState(position = location), title = "Selected Location" , snippet = "%.5f, %.5f" .format( location.latitude, location.longitude ) ) } } Column( modifier = Modifier .align(Alignment.BottomCenter) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text( text = statusText, modifier = Modifier .background(MaterialTheme.colorScheme.surface.copy(alpha = 0.85f)) .padding(8.dp) ) Button( onClick = { selectedLocation = null statusText = "Marker cleared. Tap the map to add a new marker." } ) { Text( "Clear Marker" ) } } } } } implementation(libs.maps.utils) package com.example.week10googlemap import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.maps.android.SphericalUtil import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.Marker import com.google.maps.android.compose.MarkerState import com.google.maps.android.compose.Polyline import com.google.maps.android.compose.rememberCameraPositionState class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val locationList = listOf( LatLng(-37.8770, 145.0443), // Monash University Clayton LatLng(-37.8895, 145.0938), LatLng(-37.9021, 145.2019), LatLng(-37.8880, 145.0850) ) setContent { MaterialTheme { RouteMapScreen( visitedLocations = locationList ) } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun RouteMapScreen( visitedLocations: List<LatLng>, modifier: Modifier = Modifier, title: String = "Total Walk Distance" ) { val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom( visitedLocations.firstOrNull() ?: LatLng(-37.8770, 145.0443), 12f ) } val totalDistanceKm = remember(visitedLocations) { if (visitedLocations.size < 2) { 0.0 } else { var distanceMeters = 0.0 for (i in 0 until visitedLocations.size - 1) { distanceMeters += SphericalUtil.computeDistanceBetween( visitedLocations[i], visitedLocations[i + 1] ) } distanceMeters / 1000.0 } } Scaffold( topBar = { TopAppBar( title = { Text(title) } ) } ) { innerPadding -> Column( modifier = modifier .fillMaxSize() .padding(innerPadding) ) { Text( text = "Total Walk Distance Today: %.2f km" .format(totalDistanceKm), style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(16.dp) ) GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState ) { if (visitedLocations.size > 1) { Polyline( points = visitedLocations, color = Color.Red, width = 6f ) } visitedLocations.forEachIndexed { index, location -> Marker( state = MarkerState(position = location), title = "Stop ${ index + 1 } " ) } } } } } for (i in 0 until visitedLocations.size - 1) { distanceMeters += SphericalUtil.computeDistanceBetween( visitedLocations[i], visitedLocations[i + 1] ) } package com.example.week10googlemap import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.maps.android.compose.Circle import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.Marker import com.google.maps.android.compose.MarkerState import com.google.maps.android.compose.rememberCameraPositionState class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val monashLocation = LatLng(-37.8770, 145.0443) setContent { MaterialTheme { MapScreenWithCircle( location = monashLocation ) } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun MapScreenWithCircle( location: LatLng, title: String = "Monash Clayton Campus" , circleRadiusMeters: Double = 1000.0 ) { val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(location, 15f) } Scaffold( topBar = { TopAppBar( title = { Text(title) } ) } ) { innerPadding -> Surface( modifier = Modifier .fillMaxSize() .padding(innerPadding), color = MaterialTheme.colorScheme.background ) { GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState ) { Marker( state = MarkerState(position = location), title = title ) Circle( center = location, radius = circleRadiusMeters, strokeColor = Color.Blue, strokeWidth = 4f, fillColor = Color.Blue.copy(alpha = 0.1f) ) } } } } Circle( center = location, radius = circleRadiusMeters, strokeColor = Color.Blue, strokeWidth = 4f, fillColor = Color.Blue.copy(alpha = 0.1f) ) < uses-permission android:name= " android.permission.ACCESS_COARSE_LOCATION " /> < uses-permission android:name= " android.permission.ACCESS_FINE_LOCATION " /> < meta-data android:name= " com.google.android.geo.API_KEY " android:value= " 你的 API key " /> implementation(libs.play.services.maps) implementation(libs.play.services.location) implementation(libs.maps.compose) implementation(libs.maps.utils) < uses-permission android:name= " android.permission.ACCESS_FINE_LOCATION " /> rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission() ) LatLng(latitude, longitude) LatLng(-37.8770, 145.0443) CameraPosition.fromLatLngZoom(location, 15f) Marker( state = MarkerState(position = location), title = "Monash University" ) Polyline( points = visitedLocations, color = Color.Red, width = 6f ) Circle( center = location, radius = 1000.0 )