Jak wykryć wstrząs za pomocą Androida?

117

Jak mogę wykryć wstrząsy w systemie Android? Jak mogę wykryć kierunek drgań?

Chcę zmienić obraz w widoku obrazu, gdy występuje wstrząs.

kanivel
źródło
29
Wyszukałem w Google i wylądowałem z tym pytaniem jako najlepszym wynikiem ...
zmbq
stackoverflow.com/a/54745560/4307338 możesz sprawdzić tę odpowiedź
Hoque MD Zahidul

Odpowiedzi:

174

Z punktu widzenia kodu musisz zaimplementować SensorListener:

public class ShakeActivity extends Activity implements SensorListener

Będziesz musiał nabyć SensorManager:

sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);

I zarejestruj ten czujnik z wybranymi flagami:

sensorMgr.registerListener(this,
SensorManager.SENSOR_ACCELEROMETER,
SensorManager.SENSOR_DELAY_GAME);

W metodzie onSensorChange () określasz, czy jest to wstrząs, czy nie:

public void onSensorChanged(int sensor, float[] values) {
  if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
    long curTime = System.currentTimeMillis();
    // only allow one update every 100ms.
    if ((curTime - lastUpdate) > 100) {
      long diffTime = (curTime - lastUpdate);
      lastUpdate = curTime;

      x = values[SensorManager.DATA_X];
      y = values[SensorManager.DATA_Y];
      z = values[SensorManager.DATA_Z];

      float speed = Math.abs(x+y+z - last_x - last_y - last_z) / diffTime * 10000;

      if (speed > SHAKE_THRESHOLD) {
        Log.d("sensor", "shake detected w/ speed: " + speed);
        Toast.makeText(this, "shake detected w/ speed: " + speed, Toast.LENGTH_SHORT).show();
      }
      last_x = x;
      last_y = y;
      last_z = z;
    }
  }
}

Próg drgań definiuje się jako:

private static final int SHAKE_THRESHOLD = 800;

Istnieją również inne metody wykrywania drgań. spójrz na ten link. (Jeśli ten link nie działa lub jest martwy, zajrzyj do tego archiwum WWW ).

Spójrz na ten przykład dotyczący odbiornika wykrywania wstrząsów w systemie Android.

Uwaga: SensorListener jest przestarzały. możemy użyć SensorEventListenerzamiast tego. Oto krótki przykład użycia SensorEventListener.

Dzięki.

N-JOY
źródło
16
nie używaj klasy SensorListener, ponieważ jest zdezprofilowana. użyj SensorEventListener.
picaso
1
Link jest martwy ... Oto artykuł na archive.org: web.archive.org/web/20100324212856/http://www.codeshogun.com/…
Pilot_51
stwierdzam, że czułość będzie się zmieniać w zależności od urządzenia. to, co wydawało się całkowicie akceptowalnym wykrywaniem wstrząsów w nexusie galaktyki, musi być znacznie bardziej gwałtownym wstrząsem w galaktyce III z tą samą aplikacją. jeśli sprawię, że będzie mniej czuły dla tego urządzenia, będzie zbyt wrażliwy na czymś takim jak Nexus. hmmmmmmm.
topwik
Dlaczego należy pomnożyć przez 10000 i czy w mianowniku powinny znajdować się nawiasy?
Yoda,
3
To działa, ale możesz chcieć sprawdzić matematykę, aby obliczyć przyspieszenie: Wartości zwracane przez czujniki są wm / s ^ 2 - nie ma potrzeby dzielenia przez czas. Możesz również przyjrzeć się, jak mnożysz wektory przyspieszenia.
Tad
67

Google bardzo pomaga .

/* The following code was written by Matthew Wiggins
 * and is released under the APACHE 2.0 license
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 */
package com.hlidskialf.android.hardware;

import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.content.Context;
import java.lang.UnsupportedOperationException;

public class ShakeListener implements SensorListener 
{
  private static final int FORCE_THRESHOLD = 350;
  private static final int TIME_THRESHOLD = 100;
  private static final int SHAKE_TIMEOUT = 500;
  private static final int SHAKE_DURATION = 1000;
  private static final int SHAKE_COUNT = 3;

  private SensorManager mSensorMgr;
  private float mLastX=-1.0f, mLastY=-1.0f, mLastZ=-1.0f;
  private long mLastTime;
  private OnShakeListener mShakeListener;
  private Context mContext;
  private int mShakeCount = 0;
  private long mLastShake;
  private long mLastForce;

  public interface OnShakeListener
  {
    public void onShake();
  }

  public ShakeListener(Context context) 
  { 
    mContext = context;
    resume();
  }

  public void setOnShakeListener(OnShakeListener listener)
  {
    mShakeListener = listener;
  }

  public void resume() {
    mSensorMgr = (SensorManager)mContext.getSystemService(Context.SENSOR_SERVICE);
    if (mSensorMgr == null) {
      throw new UnsupportedOperationException("Sensors not supported");
    }
    boolean supported = mSensorMgr.registerListener(this, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_GAME);
    if (!supported) {
      mSensorMgr.unregisterListener(this, SensorManager.SENSOR_ACCELEROMETER);
      throw new UnsupportedOperationException("Accelerometer not supported");
    }
  }

  public void pause() {
    if (mSensorMgr != null) {
      mSensorMgr.unregisterListener(this, SensorManager.SENSOR_ACCELEROMETER);
      mSensorMgr = null;
    }
  }

  public void onAccuracyChanged(int sensor, int accuracy) { }

  public void onSensorChanged(int sensor, float[] values) 
  {
    if (sensor != SensorManager.SENSOR_ACCELEROMETER) return;
    long now = System.currentTimeMillis();

    if ((now - mLastForce) > SHAKE_TIMEOUT) {
      mShakeCount = 0;
    }

    if ((now - mLastTime) > TIME_THRESHOLD) {
      long diff = now - mLastTime;
      float speed = Math.abs(values[SensorManager.DATA_X] + values[SensorManager.DATA_Y] + values[SensorManager.DATA_Z] - mLastX - mLastY - mLastZ) / diff * 10000;
      if (speed > FORCE_THRESHOLD) {
        if ((++mShakeCount >= SHAKE_COUNT) && (now - mLastShake > SHAKE_DURATION)) {
          mLastShake = now;
          mShakeCount = 0;
          if (mShakeListener != null) { 
            mShakeListener.onShake(); 
          }
        }
        mLastForce = now;
      }
      mLastTime = now;
      mLastX = values[SensorManager.DATA_X];
      mLastY = values[SensorManager.DATA_Y];
      mLastZ = values[SensorManager.DATA_Z];
    }
  }

}
Vincent Mimoun-Prat
źródło
3
java.lang.SecurityException: Requires VIBRATE permission
Pratik Butani,
2
SensorManager.DATA_[X,Y,Z]są teraz przestarzałe.
Rany Albeg Wein
@RanyAlbegWein czy na pewno? Sprawdziłem dokumentację i IDE i nie otrzymałem ostrzeżenia o przekreśleniu lub wycofaniu.
Neon Warge
Przestarzałe ... W większości przestarzałe wszystko.
Pratik Butani
34

Możesz również zajrzeć do biblioteki Seismic

public class Demo extends Activity implements ShakeDetector.Listener {
  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    ShakeDetector sd = new ShakeDetector(this);
    sd.start(sensorManager);

    TextView tv = new TextView(this);
    tv.setGravity(CENTER);
    tv.setText("Shake me, bro!");
    setContentView(tv, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
  }

  @Override public void hearShake() {
    Toast.makeText(this, "Don't shake me, bro!", Toast.LENGTH_SHORT).show();
  }
}
Borys
źródło
1
Niedoceniona odpowiedź.
Morgoth,
Ten jest lepszy. Ta biblioteka została stworzona przez firmę Square.
Sadda Hussain
19

Jest już wiele rozwiązań na to pytanie, ale chciałem zamieścić takie, które:

  • Nie używa biblioteki pozbawionej funkcji w API 3
  • Prawidłowo oblicza wielkość przyspieszenia
  • Prawidłowo stosuje limit czasu między wstrząsami

Oto takie rozwiązanie:

// variables for shake detection
private static final float SHAKE_THRESHOLD = 3.25f; // m/S**2
private static final int MIN_TIME_BETWEEN_SHAKES_MILLISECS = 1000;
private long mLastShakeTime;
private SensorManager mSensorMgr;

Aby zainicjować licznik czasu:

// Get a sensor manager to listen for shakes
mSensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);

// Listen for shakes
Sensor accelerometer = mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
    mSensorMgr.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}

SensorEventListener metody do przesłonięcia:

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        long curTime = System.currentTimeMillis();
        if ((curTime - mLastShakeTime) > MIN_TIME_BETWEEN_SHAKES_MILLISECS) {

            float x = event.values[0];
            float y = event.values[1];
            float z = event.values[2];

            double acceleration = Math.sqrt(Math.pow(x, 2) +
                    Math.pow(y, 2) +
                    Math.pow(z, 2)) - SensorManager.GRAVITY_EARTH;
            Log.d(APP_NAME, "Acceleration is " + acceleration + "m/s^2");

            if (acceleration > SHAKE_THRESHOLD) {
                mLastShakeTime = curTime;
                Log.d(APP_NAME, "Shake, Rattle, and Roll");
            }
        }
    }
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
    // Ignore
}

Kiedy skończysz

// Stop listening for shakes
mSensorMgr.unregisterListener(this);
Berbeć
źródło
9

Ponieważ SensorListener jest przestarzały , użyj następującego kodu:

/* put this into your activity class */
private SensorManager mSensorManager;
private float mAccel; // acceleration apart from gravity
private float mAccelCurrent; // current acceleration including gravity
private float mAccelLast; // last acceleration including gravity

private final SensorEventListener mSensorListener = new SensorEventListener() {

  public void onSensorChanged(SensorEvent se) {
    float x = se.values[0];
    float y = se.values[1];
    float z = se.values[2];
    mAccelLast = mAccelCurrent;
    mAccelCurrent = (float) Math.sqrt((double) (x*x + y*y + z*z));
    float delta = mAccelCurrent - mAccelLast;
    mAccel = mAccel * 0.9f + delta; // perform low-cut filter
  }

  public void onAccuracyChanged(Sensor sensor, int accuracy) {
  }
};

@Override
protected void onResume() {
  super.onResume();
  mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
}

@Override
protected void onPause() {
  mSensorManager.unregisterListener(mSensorListener);
  super.onPause();
}

Następnie:

/* do this in onCreate */
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
mAccel = 0.00f;
mAccelCurrent = SensorManager.GRAVITY_EARTH;
mAccelLast = SensorManager.GRAVITY_EARTH;

Pytanie z pełnymi szczegółami można znaleźć tutaj:

Android: Chcę tym potrząsnąć

Saeid N.
źródło
1
Ostatnio sprawdzałem filtr dolnoprzepustowy [alfa * prąd + (1-alfa) * poprzedni], a 0,9 (alfa?) Jest całkowicie zależne od częstotliwości próbkowania czujnika np. Dla 100ms alfa powinno wynosić 0,146 ish i nie można zakładać wszystkie urządzenia używają dokładnie tej samej częstotliwości czujnika.
escape-llc
Jestem całkiem nowy w Androidzie. Czy możesz mi pomóc zrozumieć, dlaczego onResume i onPause są wywoływane, gdy ekran się trzęsie? Nie rozumiem tej części.
Pikamander2
1
@ Pikamander2 Musimy zarejestrować słuchacza w sensorManager, zrobiliśmy to w onResume i wyrejestrowaliśmy słuchacza w onPause, więc gdy użytkownik opuści aplikację, ten odbiornik nie będzie działał, dopóki użytkownik nie wróci do aplikacji.
Saeid N.
3

To jest Kotlini używaćSensorEventListener

Utwórz nową klasę ShakeDetector

class ShakeDetector : SensorEventListener {
    private var mListener: OnShakeListener? = null
    private var mShakeTimestamp: Long = 0
    private var mShakeCount = 0
    fun setOnShakeListener(listener: OnShakeListener?) {
        mListener = listener
    }

    interface OnShakeListener {
        fun onShake(count: Int)
    }

    override fun onAccuracyChanged(
        sensor: Sensor,
        accuracy: Int
    ) { // ignore
    }

    override fun onSensorChanged(event: SensorEvent) {
        if (mListener != null) {
            val x = event.values[0]
            val y = event.values[1]
            val z = event.values[2]
            val gX = x / SensorManager.GRAVITY_EARTH
            val gY = y / SensorManager.GRAVITY_EARTH
            val gZ = z / SensorManager.GRAVITY_EARTH
            // gForce will be close to 1 when there is no movement.
            val gForce: Float = sqrt(gX * gX + gY * gY + gZ * gZ)
            if (gForce > SHAKE_THRESHOLD_GRAVITY) {
                val now = System.currentTimeMillis()
                // ignore shake events too close to each other (500ms)
                if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
                    return
                }
                // reset the shake count after 3 seconds of no shakes
                if (mShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
                    mShakeCount = 0
                }
                mShakeTimestamp = now
                mShakeCount++
                mListener!!.onShake(mShakeCount)
            }
        }
    }

    companion object {
        /*
     * The gForce that is necessary to register as shake.
     * Must be greater than 1G (one earth gravity unit).
     * You can install "G-Force", by Blake La Pierre
     * from the Google Play Store and run it to see how
     *  many G's it takes to register a shake
     */
        private const val SHAKE_THRESHOLD_GRAVITY = 2.7f
        private const val SHAKE_SLOP_TIME_MS = 500
        private const val SHAKE_COUNT_RESET_TIME_MS = 3000
    }
}

Twoja główna aktywność

class MainActivity : AppCompatActivity() {

    // The following are used for the shake detection
    private var mSensorManager: SensorManager? = null
    private var mAccelerometer: Sensor? = null
    private var mShakeDetector: ShakeDetector? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initSensor()
    }
    override fun onResume() {
        super.onResume()
        // Add the following line to register the Session Manager Listener onResume
        mSensorManager!!.registerListener(
            mShakeDetector,
            mAccelerometer,
            SensorManager.SENSOR_DELAY_UI
        )
    }

    override fun onPause() { // Add the following line to unregister the Sensor Manager onPause
        mSensorManager!!.unregisterListener(mShakeDetector)
        super.onPause()
    }

    private fun initSensor() {
        // ShakeDetector initialization
        // ShakeDetector initialization
        mSensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
        mAccelerometer = mSensorManager!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
        mShakeDetector = ShakeDetector()
        mShakeDetector!!.setOnShakeListener(object : OnShakeListener {
            override fun onShake(count: Int) { /*
                 * The following method, "handleShakeEvent(count):" is a stub //
                 * method you would use to setup whatever you want done once the
                 * device has been shook.
                 */
                Toast.makeText(this@MainActivity, count.toString(), Toast.LENGTH_SHORT).show()
            }
        })
    }
}

Na koniec dodaj ten kod do Manifestów, aby upewnić się, że telefon ma akcelerometr

<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true" />
mohsen
źródło
1

Wykonaj następujące czynności:

private float xAccel, yAccel, zAccel;
private float xPreviousAccel, yPreviousAccel, zPreviousAccel;
private boolean firstUpdate = true;
private final float shakeThreshold = 1.5f;
private boolean shakeInitiated = false;
SensorEventListener mySensorEventListener;
SensorManager mySensorManager;

Umieść to w metodzie onCreate.

mySensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    mySensorManager.registerListener(mySensorEventListener,
            mySensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
            SensorManager.SENSOR_DELAY_NORMAL);

A teraz główna część.

private boolean isAccelerationChanged() {
    float deltaX = Math.abs(xPreviousAccel - xAccel);
    float deltaY = Math.abs(yPreviousAccel - yAccel);
    float deltaZ = Math.abs(zPreviousAccel - zAccel);
    return (deltaX > shakeThreshold && deltaY > shakeThreshold)
            || (deltaX > shakeThreshold && deltaZ > shakeThreshold)
            || (deltaY > shakeThreshold && deltaZ > shakeThreshold);
}

private void updateAccelParameters(float xNewAccel, float yNewAccel, float zNewAccel) {
    if (firstUpdate) {
        xPreviousAccel = xNewAccel;
        yPreviousAccel = yNewAccel;
        zPreviousAccel = zNewAccel;
        firstUpdate = false;
    }else{
        xPreviousAccel = xAccel;
        yPreviousAccel = yAccel;
        zPreviousAccel = zAccel;
    }
    xAccel = xNewAccel;
    yAccel = yNewAccel;
    zAccel = zNewAccel;
}

private void executeShakeAction() {
    //this method is called when devices shakes
}

public void onSensorChanged(SensorEvent se) {
    updateAccelParameters(se.values[0], se.values[1], se.values[2]);
    if ((!shakeInitiated) && isAccelerationChanged()) {
        shakeInitiated = true;
    }else if ((shakeInitiated) && isAccelerationChanged()){
        executeShakeAction();
    }else if((shakeInitiated) && (!isAccelerationChanged())){
        shakeInitiated = false;
    }
}

public void onAccuracyChanged(Sensor sensor, int accuracy) {
    //setting the accuracy
}
jelic98
źródło
Próbuję wykryć, czy użytkownik trzykrotnie potrząsnął telefonem, a następnie odtworzył dźwięk ...
Si8
Kod taki (i wiele innych podobnych przykładów) wykrywa tylko długotrwały okres przyspieszenia, niekoniecznie wstrząsania. Nie wykryje ruchu tam iz powrotem, który jest w rzeczywistości przyspieszeniem, po którym następuje ujemne przyspieszenie.
escape-llc
1

Nie zapomnij dodać tego kodu do MainActivity.java:

MainActivity.java

mShaker = new ShakeListener(this);
mShaker.setOnShakeListener(new ShakeListener.OnShakeListener () {
    public void onShake() {
        Toast.makeText(MainActivity.this, "Shake " , Toast.LENGTH_LONG).show();        
    }
});

@Override
protected void onResume() {
    super.onResume();
    mShaker.resume();
}

@Override
protected void onPause() {
    super.onPause();
    mShaker.pause();
}

Albo dam ci link do tych rzeczy.

ddd
źródło