1 package se.nekotronic.simpleball;
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.util.AttributeSet;
11 import android.util.Log;
12 import android.view.View;
13
14 public class BounceView extends View {
15 // Ball position, measured in SCREENS. Starts in the middle.
16 private float ball_x = 0.5f;
17 private float ball_y = 0.5f;
18 // Ball movement, measured in SCREENS PER SECOND
19 private float ball_dx = 0.1f;
20 private float ball_dy = 0.05f;
21 // Measured in pixels
22 private int ball_radius = 10;
23
24 private int width_pixels;
25 private int height_pixels;
26
27 private Timer the_ticker;
28
29 public BounceView(Context context) {
30 super(context);
31 }
32
33 public BounceView(Context context, AttributeSet attrs) {
34 super(context, attrs);
35 }
36
37 @Override
38 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
39 super.onSizeChanged(w, h, oldw, oldh);
40 width_pixels = w;
41 height_pixels = h;
42 }
43
44 public static final double roundDouble(double d, int places) {
45 return Math.round(d * Math.pow(10, (double)places)) / Math.pow(10, (double)places);
46 }
47
48 private int frames = 0;
49
50 @Override
51 protected void onDraw(Canvas canvas) {
52 super.onDraw(canvas);
53 Paint p = new Paint();
54 int w = width_pixels > 0 ? width_pixels : canvas.getHeight();
55 int h = height_pixels > 0 ? height_pixels : canvas.getWidth();
56
57 // The ball is green when in the crosshairs
58 if (Math.sqrt(Math.pow(ball_x * w - w/2.0f, 2) + Math.pow(ball_y * h - h/2.0f, 2)) < ball_radius)
59 p.setColor(Color.GREEN);
60 else
61 p.setColor(Color.BLUE);
62 canvas.drawCircle(ball_x * w, ball_y * h, ball_radius, p);
63
64 p.setColor(Color.RED);
65 canvas.drawText("x = " + String.format("%.2f", ball_x) +
66 ", y = " + String.format("%.2f", ball_y) +
67 ", dx = " + String.format("%.2f", ball_dx) +
68 ", dy = " + String.format("%.2f", ball_dy),
69 20.0f, 20.0f, p);
70 canvas.drawText("w = " + w + " (" + width_pixels + ")" +
71 ", h = " + h + " (" + height_pixels + ")",
72 20.0f, 40.0f, p);
73 ++frames;
74 canvas.drawText("frames = " + frames,
75 20.0f, 60.0f, p);
76
77 // A crosshair at the middle of the screen
78 float crosswidth = Math.min(w, h) * 0.1f;
79 canvas.drawLine(w * 0.5f - crosswidth, h * 0.5f, w * 0.5f + crosswidth, h * 0.5f, p);
80 canvas.drawLine(w * 0.5f, h * 0.5f - crosswidth, w * 0.5f, h * 0.5f + crosswidth, p);
81
82 canvas.drawText("basen.oru.se/android",
83 20.0f, h - 5.0f, p);
84 } // onDraw
85
86 // Called when the app is visible(possibly: again). We should start moving.
87 public void resume() {
88 the_ticker = new Timer();
89 TimerTask task = new TimerTask() {
90 @Override
91 public void run() {
92 update_simulation();
93 // We get CalledFromWrongThreadException if we just just call invalidate().
94 // It must be done in the right thread!
95 Runnable r = new Runnable() {
96 @Override
97 public void run() {
98 invalidate();
99 }
100 };
101 getHandler().post(r);
102 }
103 };
104 // Give it a full second to set things up, before we start ticking
105 // (It crashed with java.lang.NullPointerException when starting after 30 ms, but worked with 40.)
106 the_ticker.schedule(task, 1000, 10);
107 } // resume
108
109 // Called when the app has been hidden. We should stop moving.
110 public void pause() {
111 nanos_when_paused = java.lang.System.nanoTime();
112 the_ticker.cancel();
113 the_ticker = null;
114 }
115
116 // -1 is not guaranteed to never happen, but we ignore that
117 private long previous_nanos = -1;
118 private long nanos_when_paused = -1;
119
120 // Calculate the ball's new position and speed
121 private void update_simulation() {
122 long now_nanos = java.lang.System.nanoTime();
123 if (previous_nanos == -1 || now_nanos < previous_nanos) {
124 // First time, or overflow, so don't update the game
125 previous_nanos = now_nanos;
126 return;
127 }
128
129 long nanos = now_nanos - previous_nanos;
130 if (nanos_when_paused != -1) {
131 // We have been paused!
132 nanos = nanos_when_paused - previous_nanos;
133 nanos_when_paused = -1;
134 }
135
136 previous_nanos = now_nanos;
137 double seconds = nanos / 1e9;
138
139 ball_x += ball_dx * seconds;
140 ball_y += ball_dy * seconds;
141
142 // Yes, this ignores that the ball has a radius.
143
144 if (ball_x < 0) {
145 Log.d("Bounce", "Bounce on left wall");
146 ball_x = -ball_x;
147 ball_dx = -ball_dx;
148 }
149 if (ball_x > 1) {
150 Log.d("Bounce", "Bounce on right wall");
151 ball_x = 2 - ball_x;
152 ball_dx = -ball_dx;
153 }
154 if (ball_y < 0) {
155 Log.d("Bounce", "Bounce on ceiling");
156 ball_y = -ball_y;
157 ball_dy = -ball_dy;
158 }
159 if (ball_y > 1) {
160 Log.d("Bounce", "Bounce on floor");
161 ball_y = 2 - ball_y;
162 ball_dy = -ball_dy;
163 }
164 } // update_simulation
165 } // class BounceView
|