Idag: Accelerometer-API:et |
Klicka på startknappen i den lilla mediaspelaren ovan för att lyssna på lektionen. (Man kan behöva vänta en stund på att ljudfilen laddas ner.) Om mediaspelaren inte syns, eller om det inte fungerar av något annat skäl, kan man klicka här för att ladda ner mp3-filen (ca 19 minuter, ca 9 megabyte). Beroende på hur webbläsaren är konfigurerad kan det kräva ett separat mp3-spelarprogram av något slag.
Ett tillägg våren 2012 (inte med på ljudspåret):
Att styra accelerometern med kommandon via telnet fungerar när jag kör en emulator med Android 2.2,
men inte när jag kör en emulator med Android 2.3.3.
Om det inte fungerar, så prova att skapa en 2.2-emulator.
Ett annat tillägg våren 2012 (inte med på ljudspåret):
På en del modernare versioner av Windows, till exempel Windows 7, finns programmet telnet inte med som default,
men det är enkelt att installera:
http://technet.microsoft.com/en-us/library/cc771275%28v=ws.10%29.aspx
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 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="se.nekotronic.simpleaccelerometer" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="7" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".SimpleAccelerometerTestActivity" android:label="@string/app_name" android:screenOrientation="nosensor"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |