Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Thêm tính năng lắc điện thoại (Shake) để làm mới ứng dụng Android

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ị).