传感器专题(3)——加速度/陀螺仪传感器

传感器专题(3)——加速度计与陀螺仪传感器

加速度计(Accelerometer)和陀螺仪(Gyroscope)是 Android 中最常用的运动传感器,分别用于检测设备的加速度(包括重力)和角速度。结合 Android 动画合集的背景,这两个传感器可以驱动动态动画效果,例如根据设备倾斜触发视图平移、旋转,或基于角速度实现游戏控制。本文将详细介绍加速度计和陀螺仪传感器的功能、工作原理、实现方法及与动画结合的示例,重点讲解如何使用 SensorManager 获取数据并应用于交互场景。

加速度计与陀螺仪传感器的作用与原理

  1. 加速度计(Accelerometer)
  • 作用:测量设备在三轴(X、Y、Z)上的加速度(单位:m/s²),包括重力加速度。常用于检测设备倾斜、运动、摇晃等。
  • 数据输出SensorEvent.values 提供三轴加速度值:
    • values[0]:X 轴加速度(向右为正)。
    • values[1]:Y 轴加速度(向上为正)。
    • values[2]:Z 轴加速度(屏幕背面为正)。
  • 典型用途
    • 检测设备倾斜(如屏幕自动旋转)。
    • 实现“摇一摇”功能。
    • 驱动动画(如倾斜触发的平移动画)。
  • 原理:通过硬件测量设备在三轴上的加速度,受重力影响(静止时 Z 轴约为 9.81 m/s²)。
  1. 陀螺仪(Gyroscope)
  • 作用:测量设备绕三轴(X、Y、Z)的角速度(单位:rad/s)。常用于检测设备旋转速度,适合游戏、AR/VR 等场景。
  • 数据输出SensorEvent.values 提供三轴角速度:
    • values[0]:绕 X 轴的角速度。
    • values[1]:绕 Y 轴的角速度。
    • values[2]:绕 Z 轴的角速度。
  • 典型用途
    • 游戏控制(如赛车游戏中的方向盘)。
    • 姿态检测(如 AR 视角调整)。
    • 动画驱动(如旋转速度控制视图旋转)。
  • 原理:通过硬件(如 MEMS 陀螺仪)测量角速度,通常不受重力影响,但可能有漂移。
  • 应用场景
  • 加速度计:倾斜设备控制 UI 元素移动(如小球滚动动画)。
  • 陀螺仪:旋转设备触发视图旋转或缩放动画。
  • 结合使用:与磁力计(前文介绍)结合,计算更精确的方向(如指南针、AR)。
  • 动画结合:传感器数据驱动 ObjectAnimator 或 Canvas 动画,增强交互性。

权限要求

加速度计和陀螺仪通常无需特殊权限,但建议检查设备是否支持:

Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer == null) {
    // 设备不支持加速度计
}

获取传感器数据

通过 SensorManager 注册监听器,获取加速度计(TYPE_ACCELEROMETER)或陀螺仪(TYPE_GYROSCOPE)数据:

SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Sensor gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(listener, gyroscope, SensorManager.SENSOR_DELAY_GAME);
  • 采样频率
  • SENSOR_DELAY_NORMAL:低频率,适合简单场景。
  • SENSOR_DELAY_UI:适中,适合 UI 更新。
  • SENSOR_DELAY_GAME:高频率,适合游戏或动画。
  • SENSOR_DELAY_FASTEST:最高频率,耗电高。

完整示例(加速度计 + 陀螺仪 + 动画)

以下是一个使用加速度计和陀螺仪驱动视图动画的示例:加速度计控制 ImageView 的平移,陀螺仪控制旋转。

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.appcompat.app.AppCompatActivity;

public class MotionSensorActivity extends AppCompatActivity {
    private SensorManager sensorManager;
    private Sensor accelerometer;
    private Sensor gyroscope;
    private ImageView movingImage;
    private TextView sensorDataText;
    private ObjectAnimator translateXAnimator;
    private ObjectAnimator rotationAnimator;
    private float lastX = 0f;
    private float lastRotation = 0f;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_motion_sensor);

        movingImage = findViewById(R.id.moving_image);
        sensorDataText = findViewById(R.id.sensor_data_text);
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

        // 检查传感器可用性
        if (accelerometer == null || gyroscope == null) {
            Toast.makeText(this, "Required sensors not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }

        // 初始化动画
        translateXAnimator = ObjectAnimator.ofFloat(movingImage, "translationX", 0f, 0f);
        translateXAnimator.setDuration(100);
        rotationAnimator = ObjectAnimator.ofFloat(movingImage, "rotation", 0f, 0f);
        rotationAnimator.setDuration(100);
    }

    @Override
    protected void onResume() {
        super.onResume();
        sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME);
        sensorManager.registerListener(sensorListener, gyroscope, SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(sensorListener);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        translateXAnimator.cancel();
        rotationAnimator.cancel();
    }

    private final SensorEventListener sensorListener = new SensorEventListener() {
        private float rotation = 0f;

        @Override
        public void onSensorChanged(SensorEvent event) {
            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                // 获取 X 轴加速度,控制平移
                float xAccel = event.values[0];
                float translationX = -xAccel * 20; // 放大效果
                translateXAnimator.setFloatValues(lastX, translationX);
                translateXAnimator.start();
                lastX = translationX;

                // 更新 UI
                sensorDataText.setText(String.format("Accel X: %.1f m/s²\nRotation: %.1f°", xAccel, rotation));
            } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
                // 获取 Z 轴角速度,控制旋转
                float zAngularVelocity = event.values[2];
                rotation += zAngularVelocity * 100; // 积分近似角度
                rotationAnimator.setFloatValues(lastRotation, rotation);
                rotationAnimator.start();
                lastRotation = rotation;

                // 更新 UI
                sensorDataText.setText(String.format("Accel X: %.1f m/s²\nRotation: %.1f°", event.values[0], rotation));
            }
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
            if (accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
                Toast.makeText(MotionSensorActivity.this, "Sensor data unreliable", Toast.LENGTH_SHORT).show();
            }
        }
    };
}
  • 布局 XML(res/layout/activity_motion_sensor.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="16dp">
    <ImageView
        android:id="@+id/moving_image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/moving_icon" />
    <TextView
        android:id="@+id/sensor_data_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Accel X: 0.0 m/s²\nRotation: 0.0°"
        android:textSize="18sp" />
</LinearLayout>
  • 说明
  • 加速度计:使用 X 轴加速度(values[0])控制 ImageView 的水平平移(translationX)。
  • 陀螺仪:使用 Z 轴角速度(values[2])积分计算旋转角度,控制 ImageView 的旋转(rotation)。
  • 动画ObjectAnimator 实现平滑的平移和旋转动画。
  • 生命周期:在 onResume 注册传感器监听,在 onPause 注销,onDestroy 取消动画。
  • UI 更新:实时显示加速度和旋转角度。
  • 资源:需要一个图标(如 res/drawable/moving_icon.png),表示移动对象。

数据平滑处理

传感器数据可能有噪声,可使用低通滤波平滑:

private float lastXAccel = 0f;
private float lastRotation = 0f;

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        float xAccel = event.values[0];
        // 低通滤波:0.9 为平滑因子
        float smoothedX = lastXAccel * 0.9f + xAccel * 0.1f;
        translateXAnimator.setFloatValues(lastX, -smoothedX * 20);
        translateXAnimator.start();
        lastXAccel = smoothedX;
    } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
        float zAngularVelocity = event.values[2];
        float rotation = lastRotation + zAngularVelocity * 100;
        rotationAnimator.setFloatValues(lastRotation, rotation);
        rotationAnimator.start();
        lastRotation = rotation;
    }
}

结合振动

结合 Vibrator(前文介绍),当加速度超过阈值(如剧烈摇晃)时触发振动:

if (Math.abs(event.values[0]) > 15) { // 剧烈运动
    Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
    } else {
        vibrator.vibrate(100);
    }
}

优缺点

  • 优点
  • 加速度计和陀螺仪提供高实时性的运动数据,适合交互式应用。
  • 可驱动丰富动画(如平移、旋转、缩放)。
  • 广泛支持,适用于大多数 Android 设备。
  • 缺点
  • 加速度计受重力影响,需处理噪声和偏移。
  • 陀螺仪可能有漂移,需定期校准。
  • 高频率采样耗电量大。
  • 替代方案
  • 线性加速度传感器TYPE_LINEAR_ACCELERATION):去除重力影响。
  • 旋转向量传感器TYPE_ROTATION_VECTOR):融合加速度计和陀螺仪。
  • Sensor Fusion Libraries:如 Google’s Sensor Hub。
  • Jetpack Compose:声明式 UI 响应传感器数据。

注意事项

  • 传感器可用性:检查 getDefaultSensor() 是否返回 null
  • 功耗:选择合适的采样频率(如 SENSOR_DELAY_GAME),避免 SENSOR_DELAY_FASTEST
  • 数据处理:使用低通滤波或均值滤波减少噪声。
  • 生命周期:在 onPause 注销传感器监听,避免资源泄漏。
  • 设备差异:不同设备传感器精度和范围可能不同,需适配。
  • 调试:通过 Log 检查传感器数据和动画效果。

扩展:Jetpack Compose 中的实现

在 Jetpack Compose 中,结合加速度计和陀螺仪驱动动画:

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween

@Composable
fun MotionSensorScreen() {
    val context = LocalContext.current
    val sensorManager = remember { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }
    val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
    val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
    var xTranslation by remember { mutableStateOf(0f) }
    var rotation by remember { mutableStateOf(0f) }
    val animatedX by animateFloatAsState(
        targetValue = xTranslation,
        animationSpec = tween(100)
    )
    val animatedRotation by animateFloatAsState(
        targetValue = rotation,
        animationSpec = tween(100)
    )

    LaunchedEffect(Unit) {
        val listener = object : SensorEventListener {
            var lastRotation = 0f
            override fun onSensorChanged(event: SensorEvent?) {
                if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
                    xTranslation = -event.values[0] * 20
                } else if (event?.sensor?.type == Sensor.TYPE_GYROSCOPE) {
                    rotation = lastRotation + event.values[2] * 100
                    lastRotation = rotation
                }
            }
            override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
        }
        sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_GAME)
        sensorManager.registerListener(listener, gyroscope, SensorManager.SENSOR_DELAY_GAME)
        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(id = R.drawable.moving_icon),
            contentDescription = "Moving Object",
            modifier = Modifier
                .size(100.dp)
                .offset(x = animatedX.dp)
                .rotate(animatedRotation)
        )
        Text(
            text = String.format("Translation X: %.1f\nRotation: %.1f°", xTranslation, rotation),
            fontSize = 18.sp
        )
    }
}
  • 说明
  • 使用 LaunchedEffect 注册传感器监听,onDispose 注销。
  • animateFloatAsState 实现平滑的平移和旋转动画。
  • 加速度计驱动 Image 的 X 轴平移,陀螺仪驱动旋转。
  • 显示实时传感器数据。

如果需要更复杂的功能(如传感器数据融合、Canvas 绘制运动轨迹、或与 WindowManager 结合创建悬浮动画),或想探讨其他传感器(如光线传感器、接近传感器)或动画效果,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注