6、实战:获取实时加速度值并显示在TextView上

好了,前面我们把加速度传感器的原理、API、参数都捋了一遍。说实话,光看不练容易忘。这一节咱们直接上手,写一个能跑起来的Demo。

目标很简单:打开App,屏幕上实时显示x、y、z三个轴的加速度值。数值会跟着你手机晃动而变化。嗯,就像你手里拿了个虚拟的水平仪。

6.1 整体思路

我个人习惯,写任何传感器相关的代码前,先想清楚三件事:

  1. 注册监听 —— 告诉系统我要用加速度传感器
  2. 处理数据 —— 在回调里拿到SensorEvent,提取三个轴的值
  3. 更新UI —— 把数值显示到TextView上

说白了,就是“监听→取值→显示”三步走。你想想看,是不是这个理?

核心逻辑流程图:
获取SensorManager getSystemService() 注册监听器 registerListener() onSensorChanged 回调接收数据 更新TextView runOnUiThread() 初始化阶段 (onCreate中完成) 运行阶段 (持续回调) ⚠ 别忘了在onPause中取消注册,避免电量浪费

6.2 布局文件

先写一个简单的布局。三个TextView分别显示x、y、z的值,再加一个整体标题。嗯,这里要注意:TextView的id要起得有意义,别用tv1、tv2这种,回头自己都看不懂。

<?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="24dp">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="实时加速度值"
        android:textSize="22sp"
        android:textStyle="bold"
        android:layout_marginBottom="32dp"/>

    <TextView
        android:id="@+id/tv_accel_x"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="X轴: 0.00 m/s²"
        android:textSize="18sp"
        android:layout_marginBottom="12dp"/>

    <TextView
        android:id="@+id/tv_accel_y"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Y轴: 0.00 m/s²"
        android:textSize="18sp"
        android:layout_marginBottom="12dp"/>

    <TextView
        android:id="@+id/tv_accel_z"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Z轴: 0.00 m/s²"
        android:textSize="18sp"/>

</LinearLayout>

6.3 Activity代码

接下来是核心代码。我习惯把传感器相关的逻辑单独封装成一个方法,这样代码看起来清爽。你在写的时候也可以这么干。

public class MainActivity extends AppCompatActivity implements SensorEventListener {

    private SensorManager sensorManager;
    private Sensor accelerometer;
    private TextView tvX, tvY, tvZ;

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

        // 初始化TextView
        tvX = findViewById(R.id.tv_accel_x);
        tvY = findViewById(R.id.tv_accel_y);
        tvZ = findViewById(R.id.tv_accel_z);

        // 获取传感器服务
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        if (sensorManager != null) {
            accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        }

        // 检查设备是否支持加速度传感器
        if (accelerometer == null) {
            tvX.setText("您的设备不支持加速度传感器");
            tvY.setVisibility(View.GONE);
            tvZ.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 我曾经在项目里忘了取消注册,结果App退到后台还在耗电
        // 被测试同学吐槽了好几次……所以现在每次都记得写
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            float x = event.values[0];
            float y = event.values[1];
            float z = event.values[2];

            // 更新UI必须在主线程
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    tvX.setText(String.format("X轴: %.2f m/s²", x));
                    tvY.setText(String.format("Y轴: %.2f m/s²", y));
                    tvZ.setText(String.format("Z轴: %.2f m/s²", z));
                }
            });
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // 精度变化时一般不需要特殊处理,但可以打日志观察
        // 我一般在这里记录一下,方便调试
    }
}
💡 小技巧: 如果你发现数值跳动太厉害,可以试试把采样率从SENSOR_DELAY_NORMAL改成SENSOR_DELAY_UI。后者频率更低,适合显示场景。我做过一个计步器项目,当时就是用了SENSOR_DELAY_UI,既省电又够用。

6.4 关键点解析

代码写完了,咱们挑几个重点聊一聊。

  • 为什么在onResume注册,onPause取消? —— 说白了,就是为了省电。App切到后台时,传感器还在工作,白白浪费电量。你想想看,用户把手机放口袋里,传感器还在疯狂采样,多冤啊。
  • 为什么用runOnUiThread? —— onSensorChanged回调是在传感器线程里执行的,不能直接更新UI。这是Android的线程模型决定的。我刚开始学的时候也踩过这个坑,直接更新TextView,结果App闪退,log里报"Only the original thread that created a view hierarchy can touch its views"。
  • 为什么检查accelerometer是否为null? —— 有些低端设备或者平板可能没有加速度传感器。如果不做检查,直接调用registerListener会抛空指针。嗯,这里要注意,用户体验要做好。

6.5 运行效果

把代码跑起来,你会看到这样的效果:

手机状态 X轴 Y轴 Z轴
平放在桌面(屏幕朝上) ≈ 0.00 ≈ 0.00 ≈ 9.81
竖屏直立 ≈ 0.00 ≈ -9.81 ≈ 0.00
横屏左侧 ≈ -9.81 ≈ 0.00 ≈ 0.00
自由落体(快速下坠) ≈ 0.00 ≈ 0.00 ≈ 0.00
⚠ 注意: 自由落体状态下三个轴都接近0,这是因为传感器本身也在做自由落体运动,相对加速度为零。这个现象在物理上叫“失重”。我在做手机防摔检测算法时,就是利用这个特征来判断手机是否处于跌落状态。

6.6 避坑指南

最后分享几个我实际项目中遇到的坑:

  • 数值抖动问题 —— 传感器原始数据会有噪声。如果你需要平滑显示,可以加一个简单的低通滤波。比如取最近5次采样的平均值。我做过一个体感游戏,不加滤波的话,画面抖得跟帕金森似的。
  • 屏幕旋转问题 —— 手机横竖屏切换时,Activity会重建。记得在onSaveInstanceState里保存传感器状态,或者在manifest里固定屏幕方向。我个人建议固定竖屏,省心。
  • 多设备兼容问题 —— 不同手机的传感器精度和采样率不一样。华为、小米、三星的传感器表现各有差异。测试时尽量多找几台真机跑一跑。

好了,这一节的内容就到这。代码量不大,但涉及的知识点挺扎实。你把这个Demo跑通了,加速度传感器的基础用法就算拿下了。


公众号:蓝海资料掘金营,微信deep3321