在一些使用地图功能的App中,通常地图上会有一个圆点或是箭头代表用户,这个圆点或者箭头在用户拿着手机转动时,朝向也会跟着改变,如下图:
本文介绍如何使用传感器获取屏幕方向实现类似的效果。
谷歌地图
我的测试设备Pixel上默认是谷歌地图,所以示例中直接使用谷歌地图。
添加依赖
在项目下的build.gradle中添加代码,如下:
scss
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
...
classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
}
}
在app module下的build.gradle中添加代码,如下:
bash
plugins {
...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
dependencies {
implementation 'com.android.volley:volley:1.2.1'
implementation 'com.google.android.libraries.maps:maps:3.1.0-beta'
}
配置API_KEY
- 在Google Cloud Console中的Google Maps Platform中获取API_KEY,如图:
- 在项目下的
local.properties
中配置API_KEY,如下:
- 在Manifest中配置API_KEY,如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
...
>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
</application>
</manifest>
实现获取屏幕方向
可以通过Android提供的屏幕方向传感器或SensorManager
的getOrientation
方法来获取设备屏幕方向。
方向角
屏幕方向传感器或SensorManager
的getOrientation
方法都会提供三个方向角数据,如下:
名称 | 备注 |
---|---|
方位角(绕z轴旋转的角度) | 此为设备当前指南针方向与磁北向之间的角度。如果设备的上边缘面朝磁北向,则方位角为 0 度;如果上边缘朝南,则方位角为 180 度。与之类似,如果上边缘朝东,则方位角为 90 度;如果上边缘朝西,则方位角为 270 度。 |
俯仰角(绕 x 轴旋转的角度) | 此为平行于设备屏幕的平面与平行于地面的平面之间的角度。如果将设备与地面平行放置,且其下边缘最靠近您,同时将设备上边缘向地面倾斜,则俯仰角将变为正值。沿相反方向倾斜(将设备上边缘向远离地面的方向移动)将使俯仰角变为负值。值的范围为 -180 度到 180 度。 |
倾侧角(绕 y 轴旋转的角度) | 此为垂直于设备屏幕的平面与垂直于地面的平面之间的角度。如果将设备与地面平行放置,且其下边缘最靠近您,同时将设备左边缘向地面倾斜,则侧倾角将变为正值。沿相反方向倾斜(将设备右边缘移向地面)将使侧倾角变为负值。值的范围为 -90 度到 90 度 |
屏幕方向传感器
通过屏幕方向传感器获取设备屏幕方向并在地图上绘制用户朝向代码如下:
kotlin
class SensorExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutSensorExampleActivityBinding
private lateinit var sensorManager: SensorManager
private var orientationSensor: Sensor? = null
private var isSensorListenerRegister = false
private val mapViewBundleKey = "MapViewBundleKey"
private var googleMap: GoogleMap? = null
private var locationManager: LocationManager? = null
private var currentLatLong: LatLng? = null
private val locationListener = LocationListener { location ->
if (currentLatLong?.latitude != location.latitude && currentLatLong?.longitude != location.longitude) {
googleMap?.run {
currentLatLong = LatLng(location.latitude, location.longitude)
// 移动地图到当前位置
animateCamera(CameraUpdateFactory.newLatLng(currentLatLong))
}
}
}
private val requestMultiplePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions: Map<String, Boolean> ->
val noGrantedPermissions = ArrayList<String>()
permissions.entries.forEach {
if (!it.value) {
noGrantedPermissions.add(it.key)
}
}
if (noGrantedPermissions.isEmpty()) {
// 申请权限通过,可以使用地图
initMapView()
} else {
//未同意授权
noGrantedPermissions.forEach {
if (!shouldShowRequestPermissionRationale(it)) {
//用户拒绝权限并且系统不再弹出请求权限的弹窗
//这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
}
}
}
}
private val sensorEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
// 传感器数据变化时回调此方法
when (event?.sensor?.type) {
Sensor.TYPE_ORIENTATION -> {
// 设置方位角旋转度数
addMarkToMap(event.values[0])
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// 传感器的精度发生变化时回调此方法,通常无需做处理
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutSensorExampleActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.includeTitle.tvTitle.text = "Sensor Example"
binding.mapView.onCreate(savedInstanceState?.getBundle(mapViewBundleKey))
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
) {
initMapView()
} else {
requestMultiplePermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding.mapView.onSaveInstanceState(outState.getBundle(mapViewBundleKey) ?: Bundle().apply {
putBundle(mapViewBundleKey, this)
})
}
override fun onStart() {
super.onStart()
binding.mapView.onStart()
}
override fun onResume() {
super.onResume()
binding.mapView.onResume()
registerSensorListener()
}
override fun onPause() {
binding.mapView.onPause()
super.onPause()
// 移除传感器监听
sensorManager.unregisterListener(sensorEventListener)
isSensorListenerRegister = false
}
override fun onStop() {
binding.mapView.onStop()
super.onStop()
}
override fun onDestroy() {
binding.mapView.onDestroy()
locationManager?.removeUpdates(locationListener)
super.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
binding.mapView.onLowMemory()
}
@SuppressLint("MissingPermission")
private fun initMapView() {
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager?.run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// 结合多种数据源(传感器、定位)提供定位信息
requestLocationUpdates(LocationManager.FUSED_PROVIDER, 2000, 0f, locationListener)
} else {
if (isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// 使用GPS提供定位信息
requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 0f, locationListener)
} else {
// 使用网络提供定位信息
requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 2000, 0f, locationListener)
}
}
}
binding.mapView.getMapAsync { googleMap ->
this.googleMap = googleMap.apply {
// 关闭谷歌地图自带的小圆点
isMyLocationEnabled = false
// 设置地图缩放等级
moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 0.0), maxZoomLevel - 5))
}
}
binding.mapView.visibility = View.VISIBLE
}
private fun addMarkToMap(rotationDegree: Float) {
googleMap?.run {
// 清除已有的Mark
clear()
currentLatLong?.let {
addMarker(MarkerOptions()
// 设置图标的位置
.position(it)
// 设置图标的旋转角度
.rotation(rotationDegree)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_device_orientation)))
}
}
}
private fun registerSensorListener() {
orientationSensor?.let {
if (!isSensorListenerRegister) {
isSensorListenerRegister = true
// 注册传感器监听并且设置数据采样延迟
// SensorManager.SENSOR_DELAY_FASTEST 延迟0微妙
// SensorManager.SENSOR_DELAY_GAME 演示20000微妙
// SensorManager.SENSOR_DELAY_UI 延迟60000微妙
// SensorManager.SENSOR_DELAY_NORMAL 延迟200000微秒
sensorManager.registerListener(sensorEventListener, it, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_GAME)
}
}
}
}
效果如图:
SensorManager
的getOrientation
方法
通过SensorManager
的getOrientation
方法获取设备屏幕方向并在地图上绘制用户朝向代码如下:
kotlin
class SensorExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutSensorExampleActivityBinding
private lateinit var sensorManager: SensorManager
private var sensor = ArrayList<Sensor?>()
private var isSensorListenerRegister = false
private val orientationAccelerometerData = FloatArray(3)
private val orientationMagnetometerData = FloatArray(3)
private val rotationMatrix = FloatArray(9)
private val orientationAngles = FloatArray(3)
private val mapViewBundleKey = "MapViewBundleKey"
private var googleMap: GoogleMap? = null
private var locationManager: LocationManager? = null
private var currentLatLong: LatLng? = null
private val locationListener = LocationListener { location ->
if (currentLatLong?.latitude != location.latitude && currentLatLong?.longitude != location.longitude) {
googleMap?.run {
currentLatLong = LatLng(location.latitude, location.longitude)
// 移动地图,显示当前位置
animateCamera(CameraUpdateFactory.newLatLng(currentLatLong))
}
}
}
private val requestMultiplePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions: Map<String, Boolean> ->
val noGrantedPermissions = ArrayList<String>()
permissions.entries.forEach {
if (!it.value) {
noGrantedPermissions.add(it.key)
}
}
if (noGrantedPermissions.isEmpty()) {
// 申请权限通过,可以使用地图
initMapView()
} else {
//未同意授权
noGrantedPermissions.forEach {
if (!shouldShowRequestPermissionRationale(it)) {
//用户拒绝权限并且系统不再弹出请求权限的弹窗
//这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
}
}
}
}
private val sensorEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
// 传感器数据变化时回调此方法
when (event?.sensor?.type) {
Sensor.TYPE_ACCELEROMETER -> {
System.arraycopy(event.values, 0, orientationAccelerometerData, 0, orientationAccelerometerData.size)
updateOrientationAngles()
}
Sensor.TYPE_MAGNETIC_FIELD -> {
System.arraycopy(event.values, 0, orientationMagnetometerData, 0, orientationMagnetometerData.size)
updateOrientationAngles()
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// 传感器的精度发生变化时回调此方法,通常无需做处理
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutSensorExampleActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.includeTitle.tvTitle.text = "Sensor Example"
binding.mapView.onCreate(savedInstanceState?.getBundle(mapViewBundleKey))
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor.add(sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER))
sensor.add(sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD))
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
) {
initMapView()
} else {
requestMultiplePermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding.mapView.onSaveInstanceState(outState.getBundle(mapViewBundleKey) ?: Bundle().apply {
putBundle(mapViewBundleKey, this)
})
}
override fun onStart() {
super.onStart()
binding.mapView.onStart()
}
override fun onResume() {
super.onResume()
binding.mapView.onResume()
registerSensorListener()
}
override fun onPause() {
binding.mapView.onPause()
super.onPause()
// 移除传感器监听
sensorManager.unregisterListener(sensorEventListener)
isSensorListenerRegister = false
}
override fun onStop() {
binding.mapView.onStop()
super.onStop()
}
override fun onDestroy() {
binding.mapView.onDestroy()
locationManager?.removeUpdates(locationListener)
super.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
binding.mapView.onLowMemory()
}
private fun updateOrientationAngles() {
// 根据加速度计传感器和磁力计传感器的读数更新旋转矩阵
SensorManager.getRotationMatrix(rotationMatrix, null, orientationAccelerometerData, orientationMagnetometerData)
// 根据旋转矩阵重新计算三个方向角
SensorManager.getOrientation(rotationMatrix, orientationAngles)
val degree = Math.toDegrees(orientationAngles[0].toDouble()).toFloat()
// 设置方位角旋转度数
addMarkToMap(degree)
}
@SuppressLint("MissingPermission")
private fun initMapView() {
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager?.run {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// 结合多种数据源(传感器、定位)提供定位信息
requestLocationUpdates(LocationManager.FUSED_PROVIDER, 2000, 0f, locationListener)
} else {
if (isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// 使用GPS提供定位信息
requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 0f, locationListener)
} else {
// 使用网络提供定位信息
requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 2000, 0f, locationListener)
}
}
}
binding.mapView.getMapAsync { googleMap ->
this.googleMap = googleMap.apply {
// 关闭谷歌地图自带的小圆点
isMyLocationEnabled = false
// 设置地图缩放等级
moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 0.0), maxZoomLevel - 5))
}
}
binding.mapView.visibility = View.VISIBLE
}
private fun addMarkToMap(rotationDegree: Float) {
googleMap?.run {
// 清除已有的Mark
clear()
currentLatLong?.let {
addMarker(MarkerOptions()
// 设置图标的位置
.position(it)
// 设置图标的旋转角度
.rotation(rotationDegree)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_device_orientation)))
}
}
}
private fun registerSensorListener() {
if (sensor.isNotEmpty() && !isSensorListenerRegister) {
isSensorListenerRegister = true
sensor.forEach { item ->
item?.let {
// 注册传感器监听并且设置数据采样延迟
// SensorManager.SENSOR_DELAY_FASTEST 延迟0微妙
// SensorManager.SENSOR_DELAY_GAME 演示20000微妙
// SensorManager.SENSOR_DELAY_UI 延迟60000微妙
// SensorManager.SENSOR_DELAY_NORMAL 延迟200000微秒
sensorManager.registerListener(sensorEventListener, it, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_GAME)
}
}
}
}
}
效果如图:
示例
演示代码已在示例Demo中添加。