Giới thiệu
Bài viết này hướng dẫn cách phát hiện cử chỉ lắc điện thoại (shake gesture) trên Android sử dụng SensorManager và Sensor.TYPE_ACCELEROMETER. Cung cấp cơ chế "Shake to refresh" hoặc "Shake for action" phổ biến trong nhiều ứng dụng.
Các bước thực hiện
Bước 1: Tạo dự án mới
Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.
Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lắc điện thoại để kích hoạt tính năng..."
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:lineSpacingExtra="8dp" />
<TextView
android:id="@+id/tvShakeCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Số lần lắc: 0"
android:textSize="24sp"
android:textColor="#2196F3" />
</LinearLayout>
Bước 3: Viết code phát hiện lắc (src/MainActivity.java)
import androidx.appcompat.app.AppCompatActivity;
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.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor accelerometer;
// Tham số phát hiện lắc
private static final float SHAKE_THRESHOLD = 12.0f; // Ngưỡng gia tốc (m/s²)
private static final int MIN_TIME_BETWEEN_SHAKES = 1000; // Thời gian tối thiểu giữa 2 lần lắc (ms)
private float lastX, lastY, lastZ;
private long lastShakeTime = 0;
private int shakeCount = 0;
private TextView tvShakeCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvShakeCount = findViewById(R.id.tvShakeCount);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager != null) {
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
// Khởi tạo giá trị ban đầu
lastX = lastY = lastZ = SensorManager.GRAVITY_EARTH;
}
@Override
protected void onResume() {
super.onResume();
if (accelerometer != null) {
// SENSOR_DELAY_UI: đủ nhạy cho shake, tiết kiệm pin hơn GAME/FASTEST
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
}
}
@Override
protected void onPause() {
super.onPause();
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}
}
@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];
// Tính gia tốc thay đổi (delta) so với lần trước
float deltaX = x - lastX;
float deltaY = y - lastY;
float deltaZ = z - lastZ;
// Cập nhật giá trị cũ
lastX = x;
lastY = y;
lastZ = z;
// Tính độ lớn vector gia tốc thay đổi
double acceleration = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);
// Phát hiện lắc: gia tốc vượt ngưỡng + đủ thời gian nghỉ
long now = System.currentTimeMillis();
if (acceleration > SHAKE_THRESHOLD && (now - lastShakeTime) > MIN_TIME_BETWEEN_SHAKES) {
lastShakeTime = now;
onShakeDetected();
}
}
private void onShakeDetected() {
shakeCount++;
runOnUiThread(() -> {
tvShakeCount.setText("Số lần lắc: " + shakeCount);
Toast.makeText(this, "Phát hiện lắc! Đang làm mới...", Toast.LENGTH_SHORT).show();
});
// TODO: Thực hiện hành động làm mới tại đây
// refreshData();
// syncServer();
// clearCache();
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Không dùng
}
}
Bước 4: Manifest (androidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.sample">
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
</intent-filter>
</activity>
</application>
</manifest>
Chạy ứng dụng
Kết nối thiết bị thật (emulator không có accelerometer thật), nhấn Run. Lắc điện thoại mạnh tay để thấy counter tăng và Toast hiện ra.
Giải thích thuật toán phát hiện lắc
| Tham số | Giá trị mặc định | Ý nghĩa |
|---|---|---|
SHAKE_THRESHOLD |
12.0f (m/s²) | Ngưỡng gia tốc. Càng lớn = càng khó kích hoạt. 9.8 = 1G (trọng lực). |
MIN_TIME_BETWEEN_SHAKES |
1000ms | Debounce: tránh đếm trùng do rung lắc dư. |
SENSOR_DELAY_UI |
~60Hz | Tần suất cập nhật. GAME/FASTEST nhạy hơn nhưng tốn pin. |
Cải tiến thuật toán (Production-ready)
public class ShakeDetector implements SensorEventListener {
private static final float SHAKE_THRESHOLD_GRAVITY = 2.7f; // 2.7G
private static final int SHAKE_SLOP_TIME_MS = 500;
private static final int SHAKE_COUNT_RESET_TIME_MS = 3000;
private OnShakeListener listener;
private long lastShakeTime;
private int shakeCount;
public interface OnShakeListener {
void onShake(int count);
}
public void setOnShakeListener(OnShakeListener listener) {
this.listener = listener;
}
@Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float gX = x / SensorManager.GRAVITY_EARTH;
float gY = y / SensorManager.GRAVITY_EARTH;
float gZ = z / SensorManager.GRAVITY_EARTH;
// G-force vector magnitude
float gForce = (float) Math.sqrt(gX * gX + gY * gY + gZ * gZ);
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
long now = System.currentTimeMillis();
// Reset count nếu quá lâu không lắc
if (now - lastShakeTime > SHAKE_COUNT_RESET_TIME_MS) {
shakeCount = 0;
}
// Debounce
if (now - lastShakeTime > SHAKE_SLOP_TIME_MS) {
lastShakeTime = now;
shakeCount++;
if (listener != null) {
listener.onShake(shakeCount);
}
}
}
}
}
Thư viện sẵn có
- ShakeDetector (Google/Android samples) - class tiện ích đơn giản.
- Seismic (Square) - Shake detection library nhẹ, dễ dùng.
implementation 'com.squareup:seismic:1.0.2'
Dùng Seismic
Seismic seismic = new Seismic(this);
seismic.addListener(() -> {
// Shake detected!
runOnUiThread(() -> refreshContent());
});
Lưu ý quan trọng
- Chỉ test trên thiết bị thật - emulator không mô phỏng accelerometer chính xác.
- Đăng ký/ hủy listener trong onResume/onPause - tiết kiệm pin, tránh leak.
- Tinh chỉnh ngưỡng cho từng loại thiết bị (tablet nhạy hơn phone).
- Tránh false positive: xe rung, đi bộ, đặt điện thoại mạnh. Cân nhắc kết hợp với gyroscope.
- Battery impact: Accelerometer tiêu tốn pin. Chỉ bật khi cần (ví dụ: màn hình đang hiển thị).