1 package se.nekotronic.simpleaccelerometer;
2
3 import java.util.Timer;
4 import java.util.TimerTask;
5
6 import android.content.Context;
7 import android.graphics.Canvas;
8 import android.graphics.Color;
9 import android.graphics.Paint;
10 import android.hardware.Sensor;
11 import android.hardware.SensorEvent;
12 import android.hardware.SensorEventListener;
13 import android.hardware.SensorManager;
14 import android.util.AttributeSet;
15 import android.util.Log;
16 import android.view.Display;
17 import android.view.Surface;
18 import android.view.View;
19 import android.view.WindowManager;
20
21 public class BounceView extends View {
22 // Ball position, measured in SCREENS. Starts in the middle.
23 private float ball_x = 0.5f;
24 private float ball_y = 0.5f;
25 // Ball movement, measured in SCREENS PER SECOND
26 private float ball_dx = 0.1f;
27 private float ball_dy = 0.05f;
28 // Measured in pixels
29 private int ball_radius = 10;
30
31 private int width_pixels;
32 private int height_pixels;
33
34 private Timer the_ticker;
35 private SensorManager sensor_manager;
36 private Sensor accelerometer;
37 private SensorEventListener accelerometer_listener;
38 private WindowManager window_manager;
39 private Display display;
40
41 private float acceleration_x = 0;
42 private float acceleration_y = 0;
43 private float acceleration_z = 0;
44
45 private void init(Context context) {
46 // We need the SensorManager to access the accelerometer
47 sensor_manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
48 accelerometer = sensor_manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
49 // We need the WindowManager to get the Display, and the Display to find the rotation
50 window_manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
51 display = window_manager.getDefaultDisplay();
52 accelerometer_listener = new SensorEventListener() {
53 @Override
54 public void onAccuracyChanged(Sensor sensor, int accuracy) {
55 // Who cares? Do nothing.
56 }
57
58 @Override
59 public void onSensorChanged(SensorEvent event) {
60 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
61 // acceleration_x = event.values[0];
62 // acceleration_y = event.values[1];
63 acceleration_z = event.values[2];
64
65 switch (display.getOrientation()) {
66
67 case Surface.ROTATION_0:
68 acceleration_x = event.values[0];
69 acceleration_y = event.values[1];
70 break;
71 case Surface.ROTATION_90:
72 acceleration_x = -event.values[1];
73 acceleration_y = event.values[0];
74 break;
75 case Surface.ROTATION_180:
76 acceleration_x = -event.values[0];
77 acceleration_y = -event.values[1];
78 break;
79 case Surface.ROTATION_270:
80 acceleration_x = event.values[1];
81 acceleration_y = -event.values[0];
82 break;
83 }
84 }
85 }
86 };
87 }
88
89 public BounceView(Context context) {
90 super(context);
91 init(context);
92 }
93
94 public BounceView(Context context, AttributeSet attrs) {
95 super(context, attrs);
96 init(context);
97 }
98
99 @Override
100 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
101 super.onSizeChanged(w, h, oldw, oldh);
102 width_pixels = w;
103 height_pixels = h;
104 }
105
106 public static final double roundDouble(double d, int places) {
107 return Math.round(d * Math.pow(10, (double)places)) / Math.pow(10, (double)places);
108 }
109
110 private int frames = 0;
111
112 @Override
113 protected void onDraw(Canvas canvas) {
114 super.onDraw(canvas);
115 Paint p = new Paint();
116 int w = width_pixels > 0 ? width_pixels : canvas.getHeight();
117 int h = height_pixels > 0 ? height_pixels : canvas.getWidth();
118
119 // The ball is green when in the crosshairs
120 if (Math.sqrt(Math.pow(ball_x * w - w/2.0f, 2) + Math.pow(ball_y * h - h/2.0f, 2)) < ball_radius)
121 p.setColor(Color.GREEN);
122 else
123 p.setColor(Color.BLUE);
124 canvas.drawCircle(ball_x * w, ball_y * h, ball_radius, p);
125
126 // Show acceleration as a white ball
127 float ax = 0.5f - 0.04f * acceleration_x;
128 float ay = 0.5f + 0.04f * acceleration_y;
129 p.setColor(Color.WHITE);
130 canvas.drawCircle(ax * w, ay * h, ball_radius, p);
131 canvas.drawLine(w * 0.5f, h * 0.5f, w * ax, h * ay, p);
132
133 p.setColor(Color.RED);
134 canvas.drawText("Ball: x = " + String.format("%.2f", ball_x) +
135 ", y = " + String.format("%.2f", ball_y) +
136 ", dx = " + String.format("%.2f", ball_dx) +
137 ", dy = " + String.format("%.2f", ball_dy),
138 20.0f, 20.0f, p);
139 canvas.drawText("w = " + w + " (" + width_pixels + ")" +
140 ", h = " + h + " (" + height_pixels + ")",
141 20.0f, 40.0f, p);
142 canvas.drawText("Acc: X = " + String.format("%.2f", acceleration_x) +
143 ", Y = " + String.format("%.2f", acceleration_y) +
144 ", Z = " + String.format("%.2f", acceleration_z),
145 20.0f, 60.0f, p);
146 ++frames;
147 canvas.drawText("frames = " + frames,
148 20.0f, 80.0f, p);
149
150 // A crosshair at the middle of the screen
151 float crosswidth = Math.min(w, h) * 0.1f;
152 canvas.drawLine(w * 0.5f - crosswidth, h * 0.5f, w * 0.5f + crosswidth, h * 0.5f, p);
153 canvas.drawLine(w * 0.5f, h * 0.5f - crosswidth, w * 0.5f, h * 0.5f + crosswidth, p);
154
155 canvas.drawText("basen.oru.se/android",
156 20.0f, h - 5.0f, p);
157 } // onDraw
158
159 // Called when the app is visible(possibly: again). We should start moving.
160 public void resume() {
161 the_ticker = new Timer();
162 TimerTask task = new TimerTask() {
163 @Override
164 public void run() {
165 update_simulation();
166 // We get CalledFromWrongThreadException if we just just call invalidate().
167 // It must be done in the right thread!
168 Runnable r = new Runnable() {
169 @Override
170 public void run() {
171 invalidate();
172 }
173 };
174 getHandler().post(r);
175 }
176 };
177 // Give it a full second to set things up, before we start ticking
178 // (It crashed with java.lang.NullPointerException when starting after 30 ms, but worked with 40.)
179 the_ticker.schedule(task, 1000, 10);
180 // Start listening to the accelerometer
181 sensor_manager.registerListener(accelerometer_listener, accelerometer, SensorManager.SENSOR_DELAY_UI);
182 } // resume
183
184 // Called when the app has been hidden. We should stop moving.
185 public void pause() {
186 nanos_when_paused = java.lang.System.nanoTime();
187 the_ticker.cancel();
188 the_ticker = null;
189 sensor_manager.unregisterListener(accelerometer_listener);
190 }
191
192 // -1 is not guaranteed to never happen, but we ignore that
193 private long previous_nanos = -1;
194 private long nanos_when_paused = -1;
195
196 // Calculate the ball's new position and speed
197 private void update_simulation() {
198 long now_nanos = java.lang.System.nanoTime();
199 if (previous_nanos == -1 || now_nanos < previous_nanos) {
200 // First time, or overflow, so don't update the game
201 previous_nanos = now_nanos;
202 return;
203 }
204
205 long nanos = now_nanos - previous_nanos;
206 if (nanos_when_paused != -1) {
207 // We have been paused!
208 nanos = nanos_when_paused - previous_nanos;
209 nanos_when_paused = -1;
210 }
211
212 previous_nanos = now_nanos;
213 double seconds = nanos / 1e9;
214
215 ball_dx -= acceleration_x / 4000;
216 ball_dy += acceleration_y / 4000;
217
218 ball_x += ball_dx * seconds;
219 ball_y += ball_dy * seconds;
220
221 // Yes, this ignores that the ball has a radius.
222
223 if (ball_x < 0) {
224 Log.d("Bounce", "Bounce on left wall");
225 ball_x = -ball_x;
226 ball_dx = -ball_dx;
227 }
228 if (ball_x > 1) {
229 Log.d("Bounce", "Bounce on right wall");
230 ball_x = 2 - ball_x;
231 ball_dx = -ball_dx;
232 }
233 if (ball_y < 0) {
234 Log.d("Bounce", "Bounce on ceiling");
235 ball_y = -ball_y;
236 ball_dy = -ball_dy;
237 }
238 if (ball_y > 1) {
239 Log.d("Bounce", "Bounce on floor");
240 ball_y = 2 - ball_y;
241 ball_dy = -ball_dy;
242 }
243 } // update_simulation
244 } // class BounceView
|