11、摇一摇功能:实现经典的摇一摇交互
摇一摇,这个功能大家再熟悉不过了。微信摇一摇、网易云音乐摇一摇换歌,甚至有些游戏里的彩蛋也是靠摇出来的。说实话,这个交互看起来简单,但真正实现起来,有不少细节需要打磨。
我个人习惯把摇一摇拆成三个核心问题:怎么检测摇动?怎么判断摇动了?摇动之后怎么响应? 今天我们就一个一个来解决。
11.1 摇一摇的核心原理
摇一摇的本质,就是监听加速度传感器的数据变化。你想想看,手机静止时,加速度计输出的值基本稳定在 (0, 0, -9.8) 左右(重力方向)。当你用力摇晃手机时,三个轴上的加速度值会剧烈波动。
所以,检测摇动的关键就是:计算加速度的变化量,看它是否超过某个阈值。
核心公式:
摇动强度 = |当前加速度 - 上次加速度| 的平方和,再开根号
说白了,就是求两个采样点之间的「变化幅度」。
11.2 基础实现:监听加速度数据
我们先从最基础的代码开始。注册一个 SensorEventListener,监听 TYPE_ACCELEROMETER 的数据。
public class ShakeDetector implements SensorEventListener {
private static final float SHAKE_THRESHOLD = 12.0f; // 摇动阈值
private static final int SHAKE_SKIP_TIME = 500; // 防抖时间(毫秒)
private static final int SHAKE_COUNT = 3; // 连续触发次数
private OnShakeListener listener;
private long lastShakeTime = 0;
private int shakeCounter = 0;
private float lastX = 0, lastY = 0, lastZ = 0;
private boolean isFirst = true;
public interface OnShakeListener {
void onShake();
}
public void setOnShakeListener(OnShakeListener listener) {
this.listener = listener;
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) return;
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
if (isFirst) {
lastX = x;
lastY = y;
lastZ = z;
isFirst = false;
return;
}
// 计算变化量
float deltaX = Math.abs(x - lastX);
float deltaY = Math.abs(y - lastY);
float deltaZ = Math.abs(z - lastZ);
// 计算摇动强度
float shakeStrength = (float) Math.sqrt(
deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ
);
// 更新上一次的值
lastX = x;
lastY = y;
lastZ = z;
// 判断是否达到阈值
if (shakeStrength > SHAKE_THRESHOLD) {
long now = System.currentTimeMillis();
if (now - lastShakeTime > SHAKE_SKIP_TIME) {
shakeCounter++;
lastShakeTime = now;
if (shakeCounter >= SHAKE_COUNT) {
shakeCounter = 0;
if (listener != null) {
listener.onShake();
}
}
}
} else {
shakeCounter = 0; // 强度不够,重置计数
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 一般不需要处理
}
}
💡 我的经验:阈值 SHAKE_THRESHOLD 设成 12.0 是我在多个项目里试出来的「甜点值」。太小了容易误触,太大了用户摇到手酸都没反应。你可以根据实际场景微调,比如游戏里可以设小一点,社交应用可以设大一点。
11.3 防抖与连续触发
你可能会问:为什么还要加 SHAKE_SKIP_TIME 和 SHAKE_COUNT?
嗯,这里要注意。加速度传感器的采样频率很高(通常几十到几百赫兹),如果你每次检测到超过阈值就触发,那用户轻轻晃一下手机,可能就触发了十几次。这显然不是我们想要的。
所以,我设计了两个机制:
- 时间防抖:两次触发之间至少间隔 500 毫秒。避免一次摇晃触发多次。
- 计数防抖:连续检测到 3 次有效摇动,才真正触发回调。这样可以过滤掉偶然的抖动。
我曾经在一个社交 App 里遇到过一个问题:用户把手机放在桌上,有人走路震动了一下桌子,结果摇一摇被触发了。后来加了计数防抖,这个问题就解决了。
11.4 在 Activity 中使用
写好了检测器,接下来就是在 Activity 里注册和注销了。这个步骤很容易被忽略,但非常重要。
public class ShakeActivity extends AppCompatActivity {
private SensorManager sensorManager;
private ShakeDetector shakeDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shake);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
shakeDetector = new ShakeDetector();
shakeDetector.setOnShakeListener(new ShakeDetector.OnShakeListener() {
@Override
public void onShake() {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在这里处理摇动后的逻辑
Toast.makeText(ShakeActivity.this, "摇一摇触发!", Toast.LENGTH_SHORT).show();
// 比如播放音效、震动反馈、切换页面等
}
});
}
});
}
@Override
protected void onResume() {
super.onResume();
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
sensorManager.registerListener(shakeDetector, accelerometer, SensorManager.SENSOR_DELAY_GAME);
}
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(shakeDetector);
}
}
⚠️ 重要提醒:一定要在 onPause() 里注销监听器!否则你的 App 退到后台后,传感器还在工作,会持续耗电。我曾经见过一个案例,开发者忘了注销,用户晚上睡觉前摇了摇手机,结果手机一晚上没休眠,第二天电量掉了 40%。
11.5 摇一摇的视觉与触觉反馈
光有逻辑还不够,用户摇动手机后,需要得到即时的反馈。我个人习惯做三件事:
- 震动反馈:调用 Vibrator 服务,让手机震一下。
- 音效反馈:播放一个短促的「嗖」或者「叮」的声音。
- 动画反馈:界面上的元素做一个缩放或旋转动画。
// 震动反馈
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
if (vibrator != null && vibrator.hasVibrator()) {
vibrator.vibrate(100); // 震动 100 毫秒
}
// 音效反馈(需要提前加载 SoundPool)
soundPool.play(shakeSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
// 动画反馈(比如让一个 ImageView 弹跳一下)
ImageView shakeIcon = findViewById(R.id.shake_icon);
shakeIcon.animate()
.scaleX(1.3f)
.scaleY(1.3f)
.setDuration(150)
.withEndAction(() -> shakeIcon.animate()
.scaleX(1.0f)
.scaleY(1.0f)
.setDuration(150)
.start())
.start();
11.6 摇一摇的完整流程
为了让你更直观地理解整个流程,我画了一张图:
11.7 常见问题与避坑指南
最后,分享几个我在实际项目中踩过的坑:
- 阈值调优:不同手机的传感器灵敏度不一样。我建议在开发阶段把阈值设成可配置的,方便测试。
- 后台耗电:前面说了,一定要在 onPause 里注销。另外,采样频率不要用 SENSOR_DELAY_FASTEST,用 SENSOR_DELAY_GAME 就足够了。
- 多轴判断:有些开发者只检测 X 轴的变化,这是不对的。用户可能朝任意方向摇晃,必须三个轴都检测。
- 模拟器测试:Android 模拟器不支持加速度传感器。你必须在真机上测试。我曾经在模拟器上调试了半天,发现传感器根本没数据,白白浪费了时间。
总结一下:摇一摇功能的核心就是「检测变化量 + 防抖 + 反馈」。代码量不大,但细节决定体验。把阈值调好、防抖做好、反馈做足,用户就会觉得「这个 App 很灵敏,很好用」。
公众号:蓝海资料掘金营,微信deep3321