如何在Android Google地图上获取和显示当前用户位置
现在许多应用程序使用位置为用户提供服务。
示例包括许多出租车骑行应用程序等优步。
用户的位置可用于显示位置特征或者显示方向。
如果我们想创建一个使用客户端的应用程序,那么我们就在运气中。
将介绍如何获取用户当前位置并在地图上显示它。
我们还可以在开发项目中使用此免费GeoCoding API。
本教程的源代码可以在Github上找到AndroidCurrentUserLocation。
首先,通过以下说明从此获取Google Maps API密钥。
获取密钥并启用计费后,我们必须使用我们可能需要的任何其他API启用Maps API。
从Android Studio创建项目。
导航 gradle.properties
并在最后添加此行,确保我们已添加API密钥。
# Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official ApiKeyMap="Insert Google Maps Key Here"
打开build.gradle(模块:app)文件和这些依赖项。
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.theitroad.trucksend" minSdkVersion 16 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { debug { buildConfigField 'String', "ApiKeyMap", ApiKeyMap resValue 'string', "api_key_map", ApiKeyMap } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' buildConfigField 'String', "ApiKeyMap", ApiKeyMap resValue 'string', "api_key_map", ApiKeyMap } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.gms:play-services-location:17.0.0' implementation 'com.google.android.gms:play-services-maps:17.0.0' implementation "com.karumi:dexter:5.0.0" testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' }
立即在右上单击同步以同步依赖项。
打开列表文件并添加以下内容:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.theitroad.trucksend"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" <uses-permission android:name="android.permission.INTERNET" <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/AppTheme" tools:ignore="GoogleAppIndexingWarning"> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" <category android:name="android.intent.category.LAUNCHER" </intent-filter> </activity> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/api_key_map" </application> </manifest>
打开活动并开始添加所需的功能:
private lateinit var googleMap: GoogleMap private lateinit var fusedLocationProviderClient: FusedLocationProviderClient override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? mapFragment!!.getMapAsync(this) fusedLocationProviderClient = FusedLocationProviderClient(this) }
我们启动Google Map,我们使用FUNDELLOCATIOPPROVIDERCLIER来获取用户的当前位置或者最后一个已知位置。
请注意,我的活动现在将扩展onmapready()。
这是必要的,以便在谷歌地图准备好显示时知道活动知道。
override fun onMapReady(map: GoogleMap?) { googleMap = map?: return }
我们必须要求用户访问其位置的许可。
我们将使用Dexter权限库来执行此操作。
首先,我们检查用户是否已授予位置。
private fun isPermissionGiven(): Boolean{ return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED }
如果未经许可权限,我们称之为方法 givePermission
要求许可。
private fun givePermission() { Dexter.withActivity(this) .withPermission(Manifest.permission.ACCESS_FINE_LOCATION) .withListener(this) .check() }
获得权限后,拨打电话 getCurrentLocation
使用FUNDELLOCATIOPPROVIDERCLIER来获取当前用户位置或者最后一个已知位置的函数。
我们用 locationRequest
要以某个间隔刷新当前用户位置以获得高精度,以便我们始终更新。
private fun getCurrentLocation() { val locationRequest = LocationRequest() locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY locationRequest.interval = (10 * 1000).toLong() locationRequest.fastestInterval = 2000 val builder = LocationSettingsRequest.Builder() builder.addLocationRequest(locationRequest) val locationSettingsRequest = builder.build() val result = LocationServices.getSettingsClient(this).checkLocationSettings(locationSettingsRequest) result.addOnCompleteListener { task -> try { val response = task.getResult(ApiException::class.java) if (response!!.locationSettingsStates.isLocationPresent){ getLastLocation() } } catch (exception: ApiException) { when (exception.statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try { val resolvable = exception as ResolvableApiException resolvable.startResolutionForResult(this, REQUEST_CHECK_SETTINGS) } catch (e: IntentSender.SendIntentException) { } catch (e: ClassCastException) { } LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { } } } } }
地理域奖金
作为一个奖励,我添加了一个gecoder方法来尝试获取所给出的地址!我还添加了一个自定义引脚标记。
private fun getLastLocation() { fusedLocationProviderClient.lastLocation .addOnCompleteListener(this) { task -> if (task.isSuccessful && task.result != null) { val mLastLocation = task.result var address = "No known address" val gcd = Geocoder(this, Locale.getDefault()) val addresses: List<Address> try { addresses = gcd.getFromLocation(mLastLocation!!.latitude, mLastLocation.longitude, 1) if (addresses.isNotEmpty()) { address = addresses[0].getAddressLine(0) } } catch (e: IOException) { e.printStackTrace() } val icon = BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(this.resources, R.drawable.ic_pickup)) googleMap.addMarker( MarkerOptions() .position(LatLng(mLastLocation!!.latitude, mLastLocation.longitude)) .title("Current Location") .snippet(address) .icon(icon) ) val cameraPosition = CameraPosition.Builder() .target(LatLng(mLastLocation.latitude, mLastLocation.longitude)) .zoom(17f) .build() googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)) } else { Toast.makeText(this, "No current location found", Toast.LENGTH_LONG).show() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_CHECK_SETTINGS -> { if (resultCode == Activity.RESULT_OK) { getCurrentLocation() } } } super.onActivityResult(requestCode, resultCode, data) }
最终活动如下所示:
package com.theitroad.trucksend import android.Manifest import android.app.Activity import android.content.Intent import android.content.IntentSender import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.location.Address import android.location.Geocoder import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import android.view.Menu import android.view.MenuItem import android.widget.Toast import androidx.core.app.ActivityCompat import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.location.* import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.SupportMapFragment import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.karumi.dexter.Dexter import com.karumi.dexter.PermissionToken import com.karumi.dexter.listener.PermissionDeniedResponse import com.karumi.dexter.listener.PermissionGrantedResponse import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.single.PermissionListener import kotlinx.android.synthetic.main.activity_main.* import java.io.IOException import java.util.* class MainActivity : AppCompatActivity(), OnMapReadyCallback, PermissionListener { companion object { const val REQUEST_CHECK_SETTINGS = 43 } private lateinit var googleMap: GoogleMap private lateinit var fusedLocationProviderClient: FusedLocationProviderClient override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? mapFragment!!.getMapAsync(this) fusedLocationProviderClient = FusedLocationProviderClient(this) } override fun onMapReady(map: GoogleMap?) { googleMap = map?: return if (isPermissionGiven()){ googleMap.isMyLocationEnabled = true googleMap.uiSettings.isMyLocationButtonEnabled = true googleMap.uiSettings.isZoomControlsEnabled = true getCurrentLocation() } else { givePermission() } } private fun isPermissionGiven(): Boolean{ return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED } private fun givePermission() { Dexter.withActivity(this) .withPermission(Manifest.permission.ACCESS_FINE_LOCATION) .withListener(this) .check() } override fun onPermissionGranted(response: PermissionGrantedResponse?) { getCurrentLocation() } override fun onPermissionRationaleShouldBeShown( permission: PermissionRequest?, token: PermissionToken? ) { token!!.continuePermissionRequest() } override fun onPermissionDenied(response: PermissionDeniedResponse?) { Toast.makeText(this, "Permission required for showing location", Toast.LENGTH_LONG).show() finish() } private fun getCurrentLocation() { val locationRequest = LocationRequest() locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY locationRequest.interval = (10 * 1000).toLong() locationRequest.fastestInterval = 2000 val builder = LocationSettingsRequest.Builder() builder.addLocationRequest(locationRequest) val locationSettingsRequest = builder.build() val result = LocationServices.getSettingsClient(this).checkLocationSettings(locationSettingsRequest) result.addOnCompleteListener { task -> try { val response = task.getResult(ApiException::class.java) if (response!!.locationSettingsStates.isLocationPresent){ getLastLocation() } } catch (exception: ApiException) { when (exception.statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try { val resolvable = exception as ResolvableApiException resolvable.startResolutionForResult(this, REQUEST_CHECK_SETTINGS) } catch (e: IntentSender.SendIntentException) { } catch (e: ClassCastException) { } LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { } } } } } private fun getLastLocation() { fusedLocationProviderClient.lastLocation .addOnCompleteListener(this) { task -> if (task.isSuccessful && task.result != null) { val mLastLocation = task.result var address = "No known address" val gcd = Geocoder(this, Locale.getDefault()) val addresses: List<Address> try { addresses = gcd.getFromLocation(mLastLocation!!.latitude, mLastLocation.longitude, 1) if (addresses.isNotEmpty()) { address = addresses[0].getAddressLine(0) } } catch (e: IOException) { e.printStackTrace() } val icon = BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(this.resources, R.drawable.ic_pickup)) googleMap.addMarker( MarkerOptions() .position(LatLng(mLastLocation!!.latitude, mLastLocation.longitude)) .title("Current Location") .snippet(address) .icon(icon) ) val cameraPosition = CameraPosition.Builder() .target(LatLng(mLastLocation.latitude, mLastLocation.longitude)) .zoom(17f) .build() googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)) } else { Toast.makeText(this, "No current location found", Toast.LENGTH_LONG).show() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_CHECK_SETTINGS -> { if (resultCode == Activity.RESULT_OK) { getCurrentLocation() } } } super.onActivityResult(requestCode, resultCode, data) } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_settings -> true else -> super.onOptionsItemSelected(item) } } }