在一些运用地图功能的App中,一般地图上会有一个圆点或是箭头代表用户,这个圆点或许箭头在用户拿着手机转动时,朝向也会跟着改动,如下图:

本文介绍如何运用传感器获取屏幕方向实现相似的作用。

谷歌地图

我的测试设备Pixel上默许是谷歌地图,所以示例中直接运用谷歌地图。

增加依赖

在项目下的build.gradle中增加代码,如下:

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

在app module下的build.gradle中增加代码,如下:

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,如图:

Android  传感器(三)— 使用传感器实现获取屏幕方向

  • 在项目下的local.properties中配置API_KEY,如下:

Android  传感器(三)— 使用传感器实现获取屏幕方向

  • 在Manifest中配置API_KEY,如下:
<?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供给的屏幕方向传感器或SensorManagergetOrientation办法来获取设备屏幕方向。

方向角

屏幕方向传感器或SensorManagergetOrientation办法都会供给三个方向角数据,如下:

称号 补白
方位角(绕z轴旋转的视点) 此为设备当前指南针方向与磁北向之间的视点。假如设备的上边际面朝磁北向,则方位角为 0 度;假如上边际朝南,则方位角为 180 度。与之相似,假如上边际朝东,则方位角为 90 度;假如上边际朝西,则方位角为 270 度。
俯仰角(绕 x 轴旋转的视点) 此为平行于设备屏幕的平面与平行于地上的平面之间的视点。假如将设备与地上平行放置,且其下边际最靠近您,一起将设备上边际向地上歪斜,则俯仰角将变为正值。沿相反方向歪斜(将设备上边际向远离地上的方向移动)将使俯仰角变为负值。值的规模为 -180 度到 180 度。
倾侧角(绕 y 轴旋转的视点) 此为垂直于设备屏幕的平面与垂直于地上的平面之间的视点。假如将设备与地上平行放置,且其下边际最靠近您,一起将设备左边际向地上歪斜,则侧倾角将变为正值。沿相反方向歪斜(将设备右边际移向地上)将使侧倾角变为负值。值的规模为 -90 度到 90 度

屏幕方向传感器

经过屏幕方向传感器获取设备屏幕方向并在地图上制作用户朝向代码如下:

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)
            }
        }
    }
}

作用如图:

Android  传感器(三)— 使用传感器实现获取屏幕方向

SensorManagergetOrientation办法

经过SensorManagergetOrientation办法获取设备屏幕方向并在地图上制作用户朝向代码如下:

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)
                }
            }
        }
    }
}

作用如图:

Android  传感器(三)— 使用传感器实现获取屏幕方向

示例

演示代码已在示例Demo中增加。

ExampleDemo github

ExampleDemo gitee