Smartphones

A quick tutorial on coding Android's accelerometer

The accelerometer is a hardware sensor used to detect a shake motion. William Francis shares the accelerometer code that he uses in his Android apps.

Hardware, software, and the user are melding in ways previously thought to be the stuff of science fiction. Hardware like touch screens, gyroscopes, and accelerometers is enabling software to detect physical user nuances. No longer is a keypad the only choice for interacting with your phone. At times simple gestures and motions can and do provide a more natural and immersive user experience.

One of the trends I've noticed is the use of a "shake" motion in a number of Android applications. Just what is a "shake" motion? Well if you've ever used an Etch-a-sketch, it's the same principle employed for erasing your work of art. Shaking your phone is a superb shortcut for things like a universal home feature, reset to defaults, random selection, and an erase or clear function.

Often the issue for developers when it comes to writing applications that take advantage of hardware sensors like the accelerometer (which is what is used to detect a shake motion) is that the coding can be hairy. An accelerometer is defined as an instrument for measuring the time rate of change of velocity with respect to magnitude or direction.  If this sounds like physics, it's because it is.  I know there are fans of the physical sciences, but I'm not one of them.

You can find a number of algorithms on the web for detecting a horizontal and/or vertical shaking motion of an Android-powered device.  Most of the algorithms work, but they tend to be complicated. This short tutorial is the accelerometer code I use in my Android applications.  I know some will argue that my implementation (which is a culmination of various resources I've found on the subject) isn't as accurate or technically astute as some of the other algorithms available, and you'll get no arguments from me.  But if you're looking for something easily digestible that gets the job done, look no further.

1. Create a new Android project in Eclipse targeted at Android version 1.6 or greater. 2. In order to give a quick visual indicator as to whether the phone is being shaken (not stirred!) in a horizontal (Figure A) or vertical (Figure B) fashion, I've added two images (horizontal.png and vertical.png ) to my /res/drawable folder. Figure A

Figure B

3. In your /res/layout folder, add a main.xml.  The layout is on the long side, but if you look closely, you'll see it's nothing special.  It consists of a table with three columns and two rows, plus an image view.
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:paddingTop="20dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center"
android:text="Shaker Demo"/>
<TableLayout
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="X-Axis"
android:gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"

android:textSize="14sp"

android:text="Y-Axis"

android:gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="Z-Axis"
android:gravity="center"/>
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/x_axis"
android:gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/y_axis"
android:gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/z_axis"
android:gravity="center"/>
</TableRow>
</TableLayout>
<ImageView
android:paddingTop="10dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/image"
android:layout_gravity="center"
android:visibility="invisible"/>
</LinearLayout>
4. In our /src folder, you will want to create a Main.java file that extends Activity and implements the SensorEventListener.  You also will need a handful of class scoped variables.  In the onCreate method, you should initialize our variables, procure an instance of the accelerometer, and register the sensor event listener.
Main.java
package com.authorwjf;
import android.app.Activity;
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.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class Main extends Activity implements SensorEventListener {
private float mLastX, mLastY, mLastZ;
private boolean mInitialized; private SensorManager mSensorManager; private Sensor mAccelerometer; private final float NOISE = (float) 2.0;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mInitialized = false;
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
}
5. Before implementing the sensor event handler (where the real meat of this app is found), you need to override some activity lifecycle management methods in the Main.java class.
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// can be safely ignored for this demo
}
Note: Many developers who publish online Android tutorials often overlook these methods when writing examples. The methods tend to muddy up the tutorial and aren't that different from one implementation to the next. We assume that developers are familiar with activity life cycle management, and when/if readers publish an app they will do the right thing. If you aren't familiar with Android's activity lifecycle, reading up on it is a must. I'm explicitly including these methods in this tutorial because now we are using the accelerometer, a hardware sensor that can run down the phone battery in no time if it's not managed correctly by an application. 6. Add your onSensorChanged handler to Main.java.  The accelerometer reports three values: an X-axis, a Y-axis, and a Z-axis.  Our function will get the delta for each axis between one function call, and the next (with a special case to handle the first time the method gets called since it takes at least two calls to the method to be able to get a delta).  Those deltas are then compared first against our NOISE constant (which you adjust to make the algorithm more or less sensitive), and then against each other to determine if the motion is in a horizontal or vertical direction.
@Override
public void onSensorChanged(SensorEvent event) {
TextView tvX= (TextView)findViewById(R.id.x_axis);
TextView tvY= (TextView)findViewById(R.id.y_axis);
TextView tvZ= (TextView)findViewById(R.id.z_axis);
ImageView iv = (ImageView)findViewById(R.id.image);
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
if (!mInitialized) {
mLastX = x;
mLastY = y;
mLastZ = z;
tvX.setText("0.0");
tvY.setText("0.0");
tvZ.setText("0.0");
mInitialized = true;
} else {
float deltaX = Math.abs(mLastX - x);
float deltaY = Math.abs(mLastY - y);
float deltaZ = Math.abs(mLastZ - z);
if (deltaX < NOISE) deltaX = (float)0.0;
if (deltaY < NOISE) deltaY = (float)0.0;
if (deltaZ < NOISE) deltaZ = (float)0.0;
mLastX = x;
mLastY = y;
mLastZ = z;
tvX.setText(Float.toString(deltaX));
tvY.setText(Float.toString(deltaY));
tvZ.setText(Float.toString(deltaZ));
iv.setVisibility(View.VISIBLE);
if (deltaX > deltaY) {
iv.setImageResource(R.drawable.horizontal);
} else if (deltaY > deltaX) {
iv.setImageResource(R.drawable.vertical);
} else {
iv.setVisibility(View.INVISIBLE);
}
}
}
If you want to see for yourself, you'll need to load the app to an actual device and give it a good shake (Figure C).  Unfortunately, when dealing with the accelerometer, there isn't a lot of shaking that can be done on the emulator. For those who would rather just import this tutorial into Eclipse directly, TechRepublic has made the source code available for download. Figure C

Shaker app in action

About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

4 comments
Mohammed Ghouse
Mohammed Ghouse

I tested in hdpi devices [ different screen sizes ].   

X,Y & Z are giving different values for hdpi devices [ different screen sizes ]

How to solve this to give uniqe [same] x,y & z values for all hdpi devices [ different screen sizes ]. And same can be applied to mdpi, xhdpi, xxhdpi devices [ different screen sizes ].

Please help me to solve this.

ISRAR KHAN
ISRAR KHAN

yes worked fine for me :) Thanks

danjarg
danjarg

Hi. Thanks for the great tutorial! I am trying to find out the maximum range of the accelerometer on Android phones. Some forums claim +-2Gs and some +-3.5Gs. The accelerometer hardware (of the LSM330 which is on the s4) has a higher range, up to 16Gs. http://www.st.com/st-web-ui/static/active/en/resource/technical/document/datasheet/DM00059856.pdf I wrote an application to practically find this range and loaded it onto an S4. The maximum range in each direction was found to be 2Gs - clearly much lower than the maximum found on the hardware's datasheet. Is there a way to increase this range and if so, how? Have you found a larger default range on other Android phones?

collins.ted
collins.ted

This is an excellent tutorial, worked the first time. Thanks.