Introduction
This tutorial guides you through creating a simple application for the Android phone. The application demonstrates using the accelerometer to make a ball move across the screen when the phone is tilted. The project is built using Eclipse and the Android SDK.
Background
This tutorial assumes you already have the Eclipse environment up and running. If you are new to Eclipse and Android development, I recommend going through the temperature converter tutorial which can be found here.
Using the Code
You can create the project by going through the steps listed below. If you prefer to load the entire project, download\unzip the project file, then open Eclipse and choose File->Import..->General->Existing Projects and choose the root folder of the TiltBall
project.
Let's begin:
Start Eclipse (I'm using Eclipse Classic version 3.6.2).
Choose File -> New -> Project -> Android -> Android Project.
Click Next.
Fill in the fields as shown below. You can use any version of Android 1.5 or later.
Click Finish.
Once the project is created, open AndroidManifest.xml.
Click on the last tab in the source editor to view the actual XML.
If you are using Android 1.6 or later, update the sdk version to 4. At version 3, phone and storage permissions will be required to install this application. Android 1.5 requires sdk version 3 (the application won't start otherwise). This value could also have been set in the "New Android Project" dialog but either way works.
<uses-sdk android:minSdkVersion="4" />
Insert the following tag to keep the phone awake while your app is running:
.......
</application>
<uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>
Update the activity tag to set the application to portrait:
<activity android:name=".TiltBallActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:configChanges="keyboardHidden|orientation">
Open main.xml and click on the second tab to see the actual XML.
Replace the existing XML with this text. We'll use the Framelayout
so the single child object (the ball) will fill the entire parent.
="1.0"="utf-8"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/main_view">
</FrameLayout>
Right-click on TiltBall
->src->droid.pkg and choose New->Class.
Enter BallView
as the class name. The other settings can stay as they are.
Click Finish.
In BallView.java class, replace the existing code with this code:
package droid.pkg;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
public class BallView extends View {
public float x;
public float y;
private final int r;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public BallView(Context context, float x, float y, int r) {
super(context);
mPaint.setColor(0xFF00FF00);
this.x = x;
this.y = y;
this.r = r;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(x, y, r, mPaint);
}
}
In TiltBallActivity.java, delete the existing code.
Enter the package
name and import
s we'll need for our application:
package droid.pkg;
import java.util.Timer;
import java.util.TimerTask;
import droid.pkg.R;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.SensorEventListener;
Create the main activity and class level variables.
public class TiltBallActivity extends Activity {
BallView mBallView = null;
Handler RedrawHandler = new Handler();
Timer mTmr = null;
TimerTask mTsk = null;
int mScrWidth, mScrHeight;
android.graphics.PointF mBallPos, mBallSpd;
Add the onCreate
handler for the main activity.
@Override
public void onCreate(Bundle savedInstanceState) {
Set window properties so our app runs full screen and the phone stays awake.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(0xFFFFFFFF,
LayoutParams.FLAG_FULLSCREEN|LayoutParams.FLAG_KEEP_SCREEN_ON);
Add the other housekeeping chores and create a pointer to the main activity.
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final FrameLayout mainView =
(android.widget.FrameLayout)findViewById(R.id.main_view);
Set the initial variable values. The ball will start center screen with a speed of zero.
Display display = getWindowManager().getDefaultDisplay();
mScrWidth = display.getWidth();
mScrHeight = display.getHeight();
mBallPos = new android.graphics.PointF();
mBallSpd = new android.graphics.PointF();
mBallPos.x = mScrWidth/2;
mBallPos.y = mScrHeight/2;
mBallSpd.x = 0;
mBallSpd.y = 0;
Create the ball object and add it to the main screen.
mBallView = new BallView(this, mBallPos.x, mBallPos.y, 5);
mainView.addView(mBallView);
mBallView.invalidate();
Create the handler for accelerometer sensor. This will adjust the ball speed according to sensor value.
((SensorManager)getSystemService(Context.SENSOR_SERVICE)).registerListener(
new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
mBallSpd.x = -event.values[0];
mBallSpd.y = event.values[1];
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
},
((SensorManager)getSystemService(Context.SENSOR_SERVICE))
.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0),
SensorManager.SENSOR_DELAY_NORMAL);
Create the handler for the touch sensor. This will move the ball to the touch point.
mainView.setOnTouchListener(new android.view.View.OnTouchListener() {
public boolean onTouch(android.view.View v, android.view.MotionEvent e) {
mBallPos.x = e.getX();
mBallPos.y = e.getY();
return true;
}});
}
Create the handler for user menu. There is only one menu entry (Exit) which is not really needed. The application will exit when the user switches to another application.
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add("Exit");
return super.onCreateOptionsMenu(menu);
}
Create the handler for menu selection. Only one choice for now.
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getTitle() == "Exit")
finish();
return super.onOptionsItemSelected(item);
}
Create the handler for onPause
event. This occurs when our application is moved to the background (user pressed Home\Back\etc). By default, the main activity thread will be paused but the background threads (i.e., Timer
) will continue to run. To fully pause our application, we will kill the Timer
thread.
@Override
public void onPause()
{
mTmr.cancel();
mTmr = null;
mTsk = null;
super.onPause();
}
Create the handler for onResume
. This means the application just started or the user has returned to our application before the OS removed it from memory.
@Override
public void onResume()
{
mTmr = new Timer();
mTsk = new TimerTask() {
public void run() {
You can log the Timer
event to confirm when the Timer
thread has ended. If you're debugging using your phone (not a virtual device), you will need to get a (free) LogCat viewer from the Android market. Log messages will not show up in the Eclipse debugger unless you use a virtual device.
android.util.Log.d("TiltBall","Timer Hit - " + mBallPos.x + ":" + mBallPos.y);
Move ball based on current ball speed (which was set in accelerometer handler).
mBallPos.x += mBallSpd.x;
mBallPos.y += mBallSpd.y;
If ball gets to edge of screen, set the ball position to opposite side of screen. Without this, the ball will just disappear. You could modify this code to have the ball stop at the edge instead of wrapping to the other side.
if (mBallPos.x > mScrWidth) mBallPos.x=0;
if (mBallPos.y > mScrHeight) mBallPos.y=0;
if (mBallPos.x < 0) mBallPos.x=mScrWidth;
if (mBallPos.y < 0) mBallPos.y=mScrHeight;
Update ball object with new position and redraw. Note that we need to redraw the ball in a background thread to prevent the main thread from locking (deadlock).
mBallView.mX = mBallPos.x;
mBallView.mY = mBallPos.y;
RedrawHandler.post(new Runnable() {
public void run() {
mBallView.invalidate();
}});
}};
The end of the onResume
handler. Start the timer with a 10 millisecond interval.
mTmr.schedule(mTsk,10,10);
super.onResume();
}
Add the onDestroy
handler. This is called when the application is stopped (not just paused). Calling killProcess
will remove the app from memory, though this is not needed as the OS will reclaim the memory when memory runs low.
@Override
public void onDestroy()
{
super.onDestroy();
System.runFinalizersOnExit(true);
android.os.Process.killProcess(android.os.Process.myPid());
}
Add the onConfigurationChanged
handler. When the user tilts the phone sideways, by default, the application will change to landscape view and call onCreate
. We want to stay in portrait view, so we capture and ignore this event.
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
}
Build the project (Project->Build All). If you have Build Automatically set, the project will already be built.
Running the app:
To see the accelerometer actually work, you will need to connect your phone to the computer:
- Connect your phone using a USB cable (you may need to install the USB drivers for your phone)
- On your phone, in Settings->Application->Development, enable USB debugging
- USB Storage should be disabled on your phone
In Eclipse, press F11 to start debugging.
After a few seconds (if everything goes right), the application should start on your phone.
If the Eclipse starts a virtual device instead:
- Close the virtual device
- Go to Run->Debug Configurations
- In the configuration settings under Target, choose Manual. This will allow you to choose which device to debug on:
Click Debug, then select Choose a running Android device in the next screen and click OK to start debugging
If you don't see any running devices, confirm that your phone is attached to your PC and you have a working USB cable. You can also download USBDeview (freeware) which lists the attached USB devices on your PC.
To exit the app, use the menu on your phone or choose Run->Terminate in Eclipse.
To install the application to your phone using an APK file:
On your phone, in Settings->Applications, enable Unknown sources to allow non-market apps on your phone
In Eclipse, choose File->Export..->Android-> Export Android Application.
Click Next.
Enter TiltBall
as the project name.
Click Next.
If you already have a keystore, choose Use existing keystore. If not, here are the steps to create one:
Choose Create new keystore. Enter a file name (no extension is needed) and a password:
Click Next.
For Alias and Password, you can use the same values you entered into the previous screen. Set validity to 100 years. Enter any name in the Name field. If you plan to publish any apps using this keystore, you should probably use your real information.
Enter the file name for your apk file.
Click Finish.
To install the apk file onto your phone, use the adb tool in the android-sdk\platform-tools folder. If you don't know the folder, just search your computer for adb.exe.
To install the apk file, use this command line:
adb install C:\TiltBall.apk
Once the install is complete, TiltBall
should be available in your phones application list.
Points of Interest
A few things worth noting:
- Writing a tutorial is harder than it looks :)
- In my testing,
onDestroy
is always called whenever the app goes to the background. According to the Android state diagram, onDestroy
is only called when the OS needs to free up memory. I'm unclear on the issue here. - To make the ball movement more realistic, the accelerometer should be used to set acceleration, not speed. This would also allow for a bounce affect when the ball hits the edge of the screen.
- The adb tool can also be used to list Android devices connected to the PC.
- For my testing, I'm used the Hauwei u8150 (Android 2.2) which ran well.
- If you see any problems or see a way to improve this tutorial, drop me a note.