You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android加速度传感器:如何判断设备是否处于水平平面

如何通过传感器判断设备水平稳定放置并实现线条旋转对齐

直接使用加速度传感器的z轴原始值作为旋转角度无法实现需求,原因是:加速度传感器输出的是设备坐标系下的加速度分量(包含重力),它和设备的实际旋转角度并非线性对应,且单独依赖加速度传感器无法准确计算设备姿态,必须结合磁场传感器推导设备在世界坐标系中的姿态角。

解决方案步骤:

  1. 计算设备姿态角
    通过SensorManager的两个核心方法将传感器数据转换为世界坐标系下的姿态角:

    • getRotationMatrix():结合加速度和磁场数据生成旋转矩阵,实现设备坐标系到世界坐标系的转换。
    • getOrientation():从旋转矩阵中提取姿态角(方位角azimuth、俯仰角pitch、滚转角roll),其中滚转角roll对应设备绕垂直轴的倾斜角度,俯仰角pitch对应设备绕水平轴的倾斜角度,二者接近0时表示设备水平。
  2. 实现线条的正确旋转
    将计算得到的roll角从弧度转换为角度,调整旋转方向后应用到Compose组件的rotationZ参数上,确保线条对齐真实水平方向。

  3. 判断水平稳定状态
    设置角度阈值(如±2°),当pitch和roll都处于阈值范围内,且连续几次传感器数据都保持稳定时,判定设备已水平放置。

完整代码示例

传感器数据处理(ViewModel中)

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.math.toDegrees

class SensorViewModel : ViewModel() {
    private val _rollAngle = MutableStateFlow(0f)
    val rollAngle: StateFlow<Float> = _rollAngle

    private val _isHorizontal = MutableStateFlow(false)
    val isHorizontal: StateFlow<Boolean> = _isHorizontal

    private var accelerometerData = FloatArray(3)
    private var magnetometerData = FloatArray(3)
    private val rotationMatrix = FloatArray(9)
    private val orientationAngles = FloatArray(3)
    private var consecutiveStableCount = 0
    private val stabilityThreshold = 3 // 连续3次数据稳定则判定为水平

    fun initSensors(context: Context) {
        val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
        val magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

        val listener = object : SensorEventListener {
            override fun onSensorChanged(event: SensorEvent?) {
                event ?: return
                when (event.sensor.type) {
                    Sensor.TYPE_ACCELEROMETER -> accelerometerData = event.values.clone()
                    Sensor.TYPE_MAGNETIC_FIELD -> magnetometerData = event.values.clone()
                }

                if (SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerData, magnetometerData)) {
                    SensorManager.getOrientation(rotationMatrix, orientationAngles)
                    // 将弧度转换为角度,roll是绕z轴的倾斜角
                    val roll = toDegrees(orientationAngles[2].toDouble()).toFloat()
                    val pitch = toDegrees(orientationAngles[1].toDouble()).toFloat()

                    _rollAngle.value = roll

                    // 判断当前是否处于水平范围
                    val currentIsHorizontal = Math.abs(pitch) < 2 && Math.abs(roll) < 2
                    // 累计稳定次数,超过阈值则更新状态
                    consecutiveStableCount = if (currentIsHorizontal) consecutiveStableCount + 1 else 0
                    _isHorizontal.value = consecutiveStableCount >= stabilityThreshold
                }
            }

            override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
        }

        sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_GAME)
        sensorManager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_GAME)
    }
}

Compose UI实现

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun HorizontalStabilityIndicator(
    viewModel: SensorViewModel = viewModel()
) {
    val rollAngle by viewModel.rollAngle.collectAsState()
    val isHorizontal by viewModel.isHorizontal.collectAsState()

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        contentPadding = PaddingValues(20.dp)
    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_horizontal_line),
            colorFilter = ColorFilter.tint(if (isHorizontal) Color.Green else Color.White),
            contentDescription = "水平对齐线条",
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    // 调整旋转方向,确保线条与真实水平对齐
                    rotationZ = -rollAngle
                }
        )

        Text(
            text = if (isHorizontal) "设备已水平稳定放置" else "请调整设备至水平",
            color = if (isHorizontal) Color.Green else Color.White,
            fontSize = 18.sp,
            modifier = Modifier.padding(top = 24.dp)
        )
    }
}

关键说明

  • 传感器采样率选择SENSOR_DELAY_GAME,平衡响应速度与系统性能消耗;
  • 稳定判断加入连续计数,避免因传感器噪声导致的误判;
  • 旋转方向取反(-rollAngle)是因为设备坐标系与UI坐标系的旋转方向差异,可根据实际图标调整。

内容的提问来源于stack exchange,提问作者Swapnil Musale

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 23:12:18