I am working on a project in which the content on the screen changes with the direction of mobile's axis. For this, I created a class which calculates azimuthal angle by accessing sensor data. This class also has a method setLine which gives back a,b,c in line equation a.x + b.y + c = 0 when supplied gps co-ordinates. This line is the z-axis of mobile.
So I created an object of this class from another class. But whenever I am accessing setLine, By seeing the log I got to know that azimuthal = NULL and oldAzimuthal = Math.PI/180 which is what I set.
I don't understand this. When I created the ViewAngleActivity object, this should have already initialized sensors and I shouldn't be getting NULL for azimuthal .
Earlier when I used ThisViewAngleActivity as the main class I didn't face such issue. I was properly getting azimthal.
Am I missing some concepts? Please help.
I am uploading the code for ViewAngleActivity
public class ViewAngleActivity extends Activity implements SensorEventListener {
Float azimuth;
Float pitch;
Float roll;
float oldAzimuth;
private SensorManager mSensorManager;
Sensor accelerometer;
Sensor magnetometer;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
Log.d("gettingViewAngle:","in onCreateSensor got Created");
}
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
mSensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI);
}
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) { }
float[] mGravity;
float[] mGeomagnetic;
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = event.values;
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mGeomagnetic = event.values;
if (mGravity != null && mGeomagnetic != null) {
float R[] = new float[9];
float I[] = new float[9];
if (SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic)) {
// orientation contains azimut, pitch and roll
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
oldAzimuth = azimuth;
azimuth = orientation[0];
pitch = orientation[1];
roll = orientation[2];
// at this point, orientation contains the azimuth(direction), pitch and roll values.
Log.d("onSensorChanged:", "azimuth = "+ azimuth);
Log.d("onSensorChanged:", "oldAzimuth = "+ oldAzimuth);
}
}
}
/**
* This method calculates line equation of mobile axis
* #param currentLatitude
* #param currentLongitude
* #return co-efficients of the line a.x + b.y + c = 0
*/
public double[] setLine(Double currentLatitude, Double currentLongitude){
double angle = 1;
double a,b,c;
double[] coEfficients = {1, 1, 0};
Log.d("setLine:", "azimuth = "+ azimuth);
Log.d("setLine:", "oldAzimuth = "+ oldAzimuth);
if(azimuth!= null) {
angle = (float) azimuth;
if (angle == 0){
angle = Math.PI/180;
}
if ( angle%((Math.PI)/2) ==0){
a = 0;
b = 1;
c = ( - currentLongitude);
}
else {
a = -(Math.tan((double) angle));
b = 1;
c = (Math.tan((double) angle) * currentLatitude) - currentLongitude;
}
Log.d("setLine:Using azimuth", "azimuth = "+ angle);
coEfficients[0] = a ;
coEfficients[1] = b ;
coEfficients[2] = c ;
}
else{
angle = (float) oldAzimuth;
if (angle == 0){
angle = Math.PI/180;
}
if ( angle%((Math.PI)/2) ==0){
a = 0;
b = 1;
c = ( - currentLongitude);
}
else {
a = -(Math.tan((double) angle));
b = 1;
c = (Math.tan((double) angle) * currentLatitude) - currentLongitude;
}
Log.d("setLine:UsingOldAzimuth", "oldAzimuth = "+ angle);
coEfficients[0] = a ;
coEfficients[1] = b ;
coEfficients[2] = c ;
}
return coEfficients;
}
}
The object I created from other class is as follows
private ViewAngleActivity viewAngleActivity;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewAngleActivity = new ViewAngleActivity();
//other parts of the code//
}
#Override
public void method1{
method2;
}
public void method1{
double[] coEfficients = viewAngleActivity.setLine(currentLatitude,currentLongitude);
}
You're only creating an instance of ViewAngleActivity, it's never really added to the window.
Your onCreate() and onResume() methods never get called, they are only called when your Activity is added to the window and goes through the Activity Lifecycle
Since you're instantiating your mSensorManager instance inside onCreate(), it never gets created and is still null.
Since onResume() isn't called (see second point up there), then your viewAngleActivity instance (which is also your SensorEventListener interface) never gets registered to mSensorManager, and as such the method onSensorChanged(SensorEvent evt) inside your ViewAngleActivity instance never gets called. Since that's where you're setting azimuth it's still going to be NULL cause that method is never called.
You might want to try a different approach, probably move all the code in your ViewAngleActivity's onCreateView() methods to a constructor like so:
//note the context parameter, pass this one when you create your ViewAngleActivity instance
public ViewAngleActivity(Context context){
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
Log.d("gettingViewAngle:","in onCreateSensor got Created");
//then you use your context to reg.
mSensorManager.registerListener(context, accelerometer, SensorManager.SENSOR_DELAY_UI);
mSensorManager.registerListener(context, magnetometer, SensorManager.SENSOR_DELAY_UI);
}
Please read more about Android's Activity Lifecycle and how they are added on screen. Note that with the above approach, you'd have to handle unregistering your SensorEventListener, cause obviously onPause() would never be called in ViewAngleActivity.
//this is how you'd probably go about it:
viewAngleActivty.mSensorManager.unregisterListener(viewAngleActivity);
Or instead, implement what you're trying to do in the new Activity you're using as your MainActivity, that would be even easier.
Related
There appear to be many old examples of getting the current cardinal direction on Android devices, but an official solution provided by Google does not appear to be in their documentation.
The oldest reference Sensor.TYPE_ORIENTATION which is deprecated, more recent ones mention Sensor.TYPE_ACCELEROMETER and Sensor.TYPE_MAGNETIC_FIELD(which I have tried with little success - accuracy shifts rapidly depending on device orientation). I've been experimenting with implementations using those two like this. I've even seen some with TYPE.GRAVITY.
The most recent seem to suggest TYPE_ROTATION_VECTOR which apparently is a fused sensor(reference), but example implementations do not seem to be readily available.
I need to use these position/motion sensors, and not GPS, because the user will not be moving during the time when this measurement is needed. Also need the measurement to be stable regardless of whether phone is flat, or vertical(as if you are taking a photo)
After we pull the degree measurement somehow, converting to cardinal direction seems to be the easy part.(https://stackoverflow.com/a/25349774/1238737)
Previous solutions
How to get Direction in Android (Such as North, West)
https://stackoverflow.com/a/11068878/1238737
I was working on open source map projects such as OsmAnd, MapsWithMe and MapBox before. I think these projects are the best available android open sources in the field of map and navigation. I've checked their codes and found that MapBox approach to show compass is stable when the phone is vertical then rotating around vertical axis (y). It uses TYPE_ROTATION_VECTOR if the rotation vector sensor is available. Otherwise it uses TYPE_ORIENTATION sensor or combination of TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD. In case of using TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD, it is possible to reduce the oscillation in result by a low-pass filter to achieve smoother values.
Here is the compass engine of MapBox and its usage.
.
LocationComponentCompassEngine.java:
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.WindowManager;
import timber.log.Timber;
import java.util.ArrayList;
import java.util.List;
/**
* This manager class handles compass events such as starting the tracking of device bearing, or
* when a new compass update occurs.
*/
public class LocationComponentCompassEngine implements SensorEventListener {
// The rate sensor events will be delivered at. As the Android documentation states, this is only
// a hint to the system and the events might actually be received faster or slower then this
// specified rate. Since the minimum Android API levels about 9, we are able to set this value
// ourselves rather than using one of the provided constants which deliver updates too quickly for
// our use case. The default is set to 100ms
private static final int SENSOR_DELAY_MICROS = 100 * 1000;
// Filtering coefficient 0 < ALPHA < 1
private static final float ALPHA = 0.45f;
// Controls the compass update rate in milliseconds
private static final int COMPASS_UPDATE_RATE_MS = 500;
private final WindowManager windowManager;
private final SensorManager sensorManager;
private final List<CompassListener> compassListeners = new ArrayList<>();
// Not all devices have a compassSensor
#Nullable
private Sensor compassSensor;
#Nullable
private Sensor gravitySensor;
#Nullable
private Sensor magneticFieldSensor;
private float[] truncatedRotationVectorValue = new float[4];
private float[] rotationMatrix = new float[9];
private float[] rotationVectorValue;
private float lastHeading;
private int lastAccuracySensorStatus;
private long compassUpdateNextTimestamp;
private float[] gravityValues = new float[3];
private float[] magneticValues = new float[3];
/**
* Construct a new instance of the this class. A internal compass listeners needed to separate it
* from the cleared list of public listeners.
*/
LocationComponentCompassEngine(WindowManager windowManager, SensorManager sensorManager) {
this.windowManager = windowManager;
this.sensorManager = sensorManager;
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
if (compassSensor == null) {
if (isGyroscopeAvailable()) {
Timber.d("Rotation vector sensor not supported on device, falling back to orientation.");
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
} else {
Timber.d("Rotation vector sensor not supported on device, falling back to accelerometer and magnetic field.");
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
}
}
public void addCompassListener(#NonNull CompassListener compassListener) {
if (compassListeners.isEmpty()) {
onStart();
}
compassListeners.add(compassListener);
}
public void removeCompassListener(#NonNull CompassListener compassListener) {
compassListeners.remove(compassListener);
if (compassListeners.isEmpty()) {
onStop();
}
}
public int getLastAccuracySensorStatus() {
return lastAccuracySensorStatus;
}
public float getLastHeading() {
return lastHeading;
}
public void onStart() {
registerSensorListeners();
}
public void onStop() {
unregisterSensorListeners();
}
#Override
public void onSensorChanged(SensorEvent event) {
// check when the last time the compass was updated, return if too soon.
long currentTime = SystemClock.elapsedRealtime();
if (currentTime < compassUpdateNextTimestamp) {
return;
}
if (lastAccuracySensorStatus == SensorManager.SENSOR_STATUS_UNRELIABLE) {
Timber.d("Compass sensor is unreliable, device calibration is needed.");
return;
}
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
rotationVectorValue = getRotationVectorFromSensorEvent(event);
updateOrientation();
// Update the compassUpdateNextTimestamp
compassUpdateNextTimestamp = currentTime + COMPASS_UPDATE_RATE_MS;
} else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
notifyCompassChangeListeners((event.values[0] + 360) % 360);
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravityValues = lowPassFilter(getRotationVectorFromSensorEvent(event), gravityValues);
updateOrientation();
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magneticValues = lowPassFilter(getRotationVectorFromSensorEvent(event), magneticValues);
updateOrientation();
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
if (lastAccuracySensorStatus != accuracy) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassAccuracyChange(accuracy);
}
lastAccuracySensorStatus = accuracy;
}
}
private boolean isGyroscopeAvailable() {
return sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
}
#SuppressWarnings("SuspiciousNameCombination")
private void updateOrientation() {
if (rotationVectorValue != null) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVectorValue);
} else {
// Get rotation matrix given the gravity and geomagnetic matrices
SensorManager.getRotationMatrix(rotationMatrix, null, gravityValues, magneticValues);
}
final int worldAxisForDeviceAxisX;
final int worldAxisForDeviceAxisY;
// Remap the axes as if the device screen was the instrument panel,
// and adjust the rotation matrix for the device orientation.
switch (windowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_90:
worldAxisForDeviceAxisX = SensorManager.AXIS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X;
break;
case Surface.ROTATION_180:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z;
break;
case Surface.ROTATION_270:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_X;
break;
case Surface.ROTATION_0:
default:
worldAxisForDeviceAxisX = SensorManager.AXIS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_Z;
break;
}
float[] adjustedRotationMatrix = new float[9];
SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX,
worldAxisForDeviceAxisY, adjustedRotationMatrix);
// Transform rotation matrix into azimuth/pitch/roll
float[] orientation = new float[3];
SensorManager.getOrientation(adjustedRotationMatrix, orientation);
// The x-axis is all we care about here.
notifyCompassChangeListeners((float) Math.toDegrees(orientation[0]));
}
private void notifyCompassChangeListeners(float heading) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassChanged(heading);
}
lastHeading = heading;
}
private void registerSensorListeners() {
if (isCompassSensorAvailable()) {
// Does nothing if the sensors already registered.
sensorManager.registerListener(this, compassSensor, SENSOR_DELAY_MICROS);
} else {
sensorManager.registerListener(this, gravitySensor, SENSOR_DELAY_MICROS);
sensorManager.registerListener(this, magneticFieldSensor, SENSOR_DELAY_MICROS);
}
}
private void unregisterSensorListeners() {
if (isCompassSensorAvailable()) {
sensorManager.unregisterListener(this, compassSensor);
} else {
sensorManager.unregisterListener(this, gravitySensor);
sensorManager.unregisterListener(this, magneticFieldSensor);
}
}
private boolean isCompassSensorAvailable() {
return compassSensor != null;
}
/**
* Helper function, that filters newValues, considering previous values
*
* #param newValues array of float, that contains new data
* #param smoothedValues array of float, that contains previous state
* #return float filtered array of float
*/
private float[] lowPassFilter(float[] newValues, float[] smoothedValues) {
if (smoothedValues == null) {
return newValues;
}
for (int i = 0; i < newValues.length; i++) {
smoothedValues[i] = smoothedValues[i] + ALPHA * (newValues[i] - smoothedValues[i]);
}
return smoothedValues;
}
/**
* Pulls out the rotation vector from a SensorEvent, with a maximum length
* vector of four elements to avoid potential compatibility issues.
*
* #param event the sensor event
* #return the events rotation vector, potentially truncated
*/
#NonNull
private float[] getRotationVectorFromSensorEvent(#NonNull SensorEvent event) {
if (event.values.length > 4) {
// On some Samsung devices SensorManager.getRotationMatrixFromVector
// appears to throw an exception if rotation vector has length > 4.
// For the purposes of this class the first 4 values of the
// rotation vector are sufficient (see crbug.com/335298 for details).
// Only affects Android 4.3
System.arraycopy(event.values, 0, truncatedRotationVectorValue, 0, 4);
return truncatedRotationVectorValue;
} else {
return event.values;
}
}
public static float shortestRotation(float heading, float previousHeading) {
double diff = previousHeading - heading;
if (diff > 180.0f) {
heading += 360.0f;
} else if (diff < -180.0f) {
heading -= 360.f;
}
return heading;
}
}
CompassListener.java:
/**
* Callbacks related to the compass
*/
public interface CompassListener {
/**
* Callback's invoked when a new compass update occurs. You can listen into the compass updates
* using {#link LocationComponent#addCompassListener(CompassListener)} and implementing these
* callbacks. Note that this interface is also used internally to to update the UI chevron/arrow.
*
* #param userHeading the new compass heading
*/
void onCompassChanged(float userHeading);
/**
* This gets invoked when the compass accuracy status changes from one value to another. It
* provides an integer value which is identical to the {#code SensorManager} class constants:
* <ul>
* <li>{#link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT}</li>
* <li>{#link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE}</li>
* <li>{#link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW}</li>
* <li>{#link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM}</li>
* <li>{#link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH}</li>
* </ul>
*
* #param compassStatus the new accuracy of this sensor, one of
* {#code SensorManager.SENSOR_STATUS_*}
*/
void onCompassAccuracyChange(int compassStatus);
}
MainActivity.java:
import android.content.Context;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.WindowManager;
import android.widget.TextView;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private LocationComponentCompassEngine compassEngine;
private float previousCompassBearing = -1f;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = findViewById(R.id.textView);
CompassListener compassListener = new CompassListener() {
#Override
public void onCompassChanged(float targetCompassBearing) {
if (previousCompassBearing < 0) {
previousCompassBearing = targetCompassBearing;
}
float normalizedBearing =
LocationComponentCompassEngine.shortestRotation(targetCompassBearing, previousCompassBearing);
previousCompassBearing = targetCompassBearing;
String status = "NO_CONTACT";
switch (compassEngine.getLastAccuracySensorStatus()) {
case SensorManager.SENSOR_STATUS_NO_CONTACT:
status = "NO_CONTACT";
break;
case SensorManager.SENSOR_STATUS_UNRELIABLE:
status = "UNRELIABLE";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_LOW:
status = "ACCURACY_LOW";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:
status = "ACCURACY_MEDIUM";
break;
case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:
status = "ACCURACY_HIGH";
break;
}
textView.setText(String.format(Locale.getDefault(),
"CompassBearing: %f\nAccuracySensorStatus: %s", normalizedBearing, status));
}
#Override
public void onCompassAccuracyChange(int compassStatus) {
}
};
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager);
compassEngine.addCompassListener(compassListener);
compassEngine.onStart();
}
#Override
protected void onDestroy() {
super.onDestroy();
compassEngine.onStop();
}
}
I am trying to use multiple sensors like Accelerator,Magnetic Field, Light and so on and for each sensor I wrote an individual class which not an activity , each of them has its SensorEventListener as well. What I want to do is when user chooses one of them I start to show the data on a fragment(on MainActivity) , when user changes the previous sensor should stop and new one should start. However, when I try to stop previous one by unregistering its listener , it doesn't unregister but it registers and works. I want to stop previous listener. What is wrong? Any ideas?
Here is the sensor class;
public class Accelerometer
{
private SensorManager sensorManager;
private Sensor sensor;
public List<ObjAccelerometer> lstData;
ObjAccelerometer currentData;
float lastX,lastY,lastZ;
String currentTime;
int numberOfSamples;
Context context;
public Accelerometer(Context _context,int _numberSample)
{
context=_context;
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
numberOfSamples=_numberSample;
lstData=new ArrayList<>();
}
public void registerUnregister(boolean register)
{
if(register)
sensorManager.registerListener(mSensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST);
else
sensorManager.unregisterListener(mSensorListener);
}
Calendar time;
private final SensorEventListener mSensorListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent event) {
if(numberOfSamples>lstData.size()) {
if (currentData != null) {
lastX = currentData.get_x();
lastY = currentData.get_y();
lastZ = currentData.get_z();
}
currentData = new ObjAccelerometer();
time = Calendar.getInstance();
currentTime = time.get(Calendar.HOUR) + ":" + time.get(Calendar.MINUTE) + ":" + time.get(Calendar.SECOND) + ":" + time.get(Calendar.MILLISECOND);
currentData.set_time(currentTime);
currentData.set_x(event.values[0]);
currentData.set_y(event.values[1]);
currentData.set_z(event.values[2]);
Float speed = Math.abs(event.values[0] + event.values[1] + event.values[2] - lastX - lastY - lastZ);
currentData.set_speed(speed);
lstData.add(currentData);
Util.createToaster(context, "X Y Z Time:" + currentData.toString());
}
else
registerUnregister(false);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
}
Here the code for calling them ;
unRegisterAllSensors();
switch (selectedSensor) {
case Accelerometer:
accelerometer= new Accelerometer(context, DEFAULT_SAMPLE_NUMBER);
accelerometer.registerUnregister(true);
lstAccelerometer = accelerometer.lstData;
break;
case Linear_Accelerometer:
linearAccelerometer= new LinearAccelerometer(context, DEFAULT_SAMPLE_NUMBER);
linearAccelerometer.registerUnregister(true);
break;
....
Here is the unRegisterAllSensors() function code:
if(accelerometer!=null) {
accelerometer.registerUnregister(false);
accelerometer=null;
}
if(linearAccelerometer!=null) {
linearAccelerometer.registerUnregister(false);
linearAccelerometer=null;
}
Sorry about bothering people,it actually works!
I made a few changes including setting SensorManager final parameter and also removing Toast message and add A toaster only for register and unregister of each listener. It worked! I believe the problem was caused by two things;
1-Toasting the message is slower process than sensing because of that even though I unregistered messages were still on the screen for a while.
2- when I didn't define SensorManager final , every time I was getting a new instance of it so size of the listener list was 0.
Still, this can be a good sample for people who want to call sensors from non-activity classes.
Thanks!
I'm having trouble recording the Z-axis data from the accelerometer in an array.
I think I'm probably failing on some basic java rules, but here's what I'm trying to do:
private ArrayList<Float[]> z = new ArrayList<Float[]>();
protected void onCreate(Bundle savedInstanceState) {
SensorManager manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor accelerometer = manager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
z.add(event.values[2]);
}
But whenever I try to add to the arraylist I get:
"The method add(Float[]) in the type ArrayList is not applicable for the arguments (float)"
How can I add the z axis data to an array?
Its because your ArrayList of Float[] type.
Replace the following,
private ArrayList<Float[]> z = new ArrayList<Float[]>();
with
ArrayList<Float> z = new ArrayList<Float>();
I have read through a lot of code but I do not understand how you can make an image move using the accelerometer sensor, I understand how to register it but I do not understand how to actually make an image or a shape draw move in sync with the accelerometer axis, I am using android java to do this. Please can someone help me as I am really struggling. Thank you for your time and help.
So, here's the code to register a listener (I know you said you've done this already, but it can never hurt):
private void enableAccelerometerListening() {
sensorManager = (SensorManager) getSystemService(COntext.SENSOR_SERVICE);
sensorManager.registerListener(sensorEventListener), sensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
}
private void disableAccelerometerListening() {
if (sensorManager != null) {
sesnsorManager.unregisterListener(sensorEVentListener, sensorManager.getDefaultSensor(SensorManager.SENSOR_ACCELEROMETER));
sensorManager = null;
}}
You will need a couple fields just below your class declaration:
private SesnsorManager sensorManager;
private float acceleration;
private float currentAcceleration;
private float lastAcceleration;
private static final int ACCELERATION_THRESHOLD = 15000;
Here is the event handler, which gets very close to what you need help with:
private SensorEventListener sensorEventListener = new SensorEventListener() {
public void onSesnsorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
lastAcceleration = currentAcceleration; //save previous accel value
currentAcceleration = x*x + y*y + z*z;
acceleration = currentAcceleration * (currentAcceleration - lastAcceleration); // calc the change in acceleration
//if the accel is above a certain threshold:
if (acceleration > ACCELERATION_THRESHOLD) {
//MAKE YOUR CODE HERE THAT RESPONDS TO ACCELERATION EVENTS
//Note, your accel threshold should be determined by trial and error on a number of devices
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy {}
};
Also, I'll try to address some of your animation needs, though I am much more spotty in this area. I imagine that what you need to do is make your image move as the accelerometer detects moves. The image will have to be moved via an animation, rather than the accelerometer directly. So say 'spot' is your image, okay? (the code below both adds a spot and sets up its animations(which are not directly tied to the accelerometer, but I hope this will be helpful nonetheless):
public void addSpot() {
int x = random.nextInt(viewWidth - SPOT_DIAMETER);
int y = random.nextInt(viewHeight = SPOT_DIAMETER);
int x2 = random.nextInt(viewWidth - SPOT_DIAMETER);
int y2 = random.nextInt(viewWidth - SPOT_DIAMETER);
final ImageView spot = (ImageView) layoutFinlater.inflate(R.layout.untouched, null);
spot.setLayoutParams(new RelativeLayout.LayoutParams(SPOT_DIAMETER, SPOT_DIAMETER));
spot.setX(x);
spot.setY(y);
Well right here is where I think you could start doing something with the accelerometer events...
As you saw in my other response above,
if (acceleration > ACCELERATION_THRESHOLD) {
spot.animate().x(x2).y(y2).setDuration(animationTime);
animationTime will just be something in milliseconds that you feel is appropriate, and don't forget to take care of importing the necessary packages.
I am trying to get the coordinates of the image on the screen. I currently have an ImageView within an activity. I understand that the getLocationOnScreen() method can only be called once the layout has been created, so calling this method within the oncreate function would return [0,0]. But I do not know how to get this method to return the correct values. I have tried overiding various superclass methods, like the onstart method or the onTouchEvent method and it still returns [0,0] to me. The code I currently have is as follows:
#Override
public void onCreate(Bundle savedInstanceState)
{
// Some code here after which ..
image = (ImageView) findViewById(R.id.imageVfi);
image.setImageBitmap(imageData.entrySet().iterator().next().getValue());
}
Then I have the onStart method which I have overriden
#Override
public void onStart()
{
super.onStart();
image.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
int[] dim = new int[2];
image.getLocationOnScreen(dim);
new AlertDialog.Builder(DisplayPicture.this)
.setIcon(R.drawable.icon)
.setTitle("Touch coordinates : " +
String.valueOf(dim[0]) + "x" + String.valueOf(dim[1]))
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
}
}).show();
// image calculations go here
return true;
}
});
}
This returns 0X0 to me. Any help is much appreciated.
ImageView does not offer a way to get the location of its image on screen. You can only query the location of the ImageView itself. You can try to use ImageView.getImageMatrix() but I'm not sure we populate it when the image is simply centered (as opposed to scaled, etc.)
Solution after following advice given above
Solution:
Following the advice provided by Romain Guy below, I was able to get the co-ordinates but adding the following code to the onStart overidden method.
display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
int orientation = display.getOrientation();
float rowStart=0, rowEnd=0, columnStart=0, columnEnd=0;
if(orientation ==0)
{
final Matrix matrix = image.getImageMatrix();
float[] values = new float[9];
matrix.getValues(values);
rowStart = values[0];
columnStart = values[5];
rowEnd = image.getWidth()-rowStart;
columnEnd = image.getHeight()-columnStart;
}else if(orientation == 1)
{
final Matrix matrix = image.getImageMatrix();
float[] values = new float[9];
matrix.getValues(values);
rowStart = values[2];
columnStart = values[3];
rowEnd = image.getWidth()-rowStart;
columnEnd = image.getHeight()-columnStart;
}
If it's only the image view that you have in your activity, then it will be positioned by default in the top left corner with coordinates 0x0.