Working on a consulting project. A last minute requirement is balls bouncing around on the screen (don't ask why...sigh)
Anyways...these balls are grouped with values. 10 balls are RED worth 100 points. 5 balls are BLUE worth 50 points. 5 balls are GREEN worth 25 points. 5 balls are YELLOW worth 10 points.
With that background the approach that I've taken is to extend a SurfaceView and define 5 threads each of which manages a particular groups of balls.
Each thread receives the same SurfaceHolder from the SurfaceView.
The reason I've chosen multiple threads instead of just one is because the performance of managing all of the balls onscreen is not the greatest.
OpenGL is not really an option right now.
Here's an example of one of the thread classes. When the thread is run, it creates a certain number of balls. Each ball is randomly created and added to a list.
public class hundred_balls_thread extends base_balls_thread {
public hundred_balls_thread(SurfaceHolder holder, Context ctext, int radius) {
super(holder, ctext, radius);
}
@Override
public void run() {
int x, y, radius;
while (Calorie_balls.size() <= 21) {
x = 100 + (int) (Math.random() * (mCanvasWidth - 200));
y = 100 + (int) (Math.random() * (mCanvasHeight) - 200);
radius = mRadius;
if ((x - mRadius) < 0) {
x = x + mRadius;
}
if ((x + mRadius) > mCanvasWidth) {
x = x - mRadius;
}
if ((y + mRadius) > mCanvasHeight)
y = y - mRadius;
if ((y - mRadius) < 0)
y = y + mRadius;
calorie_ball ball = new calorie_ball(x, y, radius, context.getResources().getColor(R.color.red100ball), "100");
boolean addit = true;
Calorie_balls.add(ball);
}
super.run();
}
}
Here's the base class that they all extend:
public class base_balls_thread extends Thread {
protected int mCanvasWidth;
protected int mCanvasHeight;
protected int mRadius;
protected Context context;
public ArrayList<calorie_ball> Calorie_balls = new ArrayList<calorie_ball>(); // Dynamic array with dots
private SurfaceHolder holder;
private boolean running = false;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint text_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int refresh_rate = 100; // How often we update the screen, in ms
public base_balls_thread(SurfaceHolder holder, Context ctext, int radius) {
this.holder = holder;
context = ctext;
mRadius = radius;
}
@Override
public void run() {
long previousTime, currentTime;
previousTime = System.currentTimeMillis();
Canvas canvas = null;
while (running) {
// Look if time has past
currentTime = System.currentTimeMillis();
while ((currentTime - previousTime) < refresh_rate) {
currentTime = System.currentTimeMillis();
}
previousTime = currentTime;
try {
// PAINT
try {
canvas = holder.lockCanvas();
synchronized (holder) {
draw(canvas);
}
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
// WAIT
try {
Thread.sleep(refresh_rate); // Wait some time till I need to display again
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (Exception eal) {
String msg = eal.getMessage();
if (msg == null)
msg = "Blahbla";
}
}
}
// The actual drawing in the Canvas (not the update to the screen).
private void draw(Canvas canvas) {
// dot temp_dot;
canvas.drawColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(4);
text_paint.setColor(Color.BLACK);
text_paint.setTextSize(40);
try {
for (calorie_ball crcl : Calorie_balls) {
paint.setColor(crcl.color);
paint.setShader(new RadialGradient(crcl.x + 10, crcl.y, crcl.radius * 2, crcl.color, Color.BLACK, Shader.TileMode.CLAMP));
if (crcl.x + crcl.radius < 0 && crcl.y + crcl.radius < 0) {
crcl.x = canvas.getWidth() / 2;
crcl.y = canvas.getHeight() / 2;
} else {
crcl.x += crcl.xVelocity;
crcl.y += crcl.yVelocity;
if ((crcl.x > canvas.getWidth() - crcl.radius) || (crcl.x - crcl.radius < 0)) {
crcl.xVelocity = crcl.xVelocity * -1;
}
if ((crcl.y > canvas.getHeight() - crcl.radius) || (crcl.y - crcl.radius < 0)) {
crcl.yVelocity = crcl.yVelocity * -1;
}
}
String calval = crcl.get_calorie_value();
int x = crcl.x + 5;
int y = crcl.y + 5;
canvas.drawCircle(crcl.x, crcl.y, crcl.radius, paint);
canvas.drawText(calval, x, y, text_paint);
}
} catch (Exception ep) {
String b = ep.getMessage();
if (b == null)
b = "blah";
}
}
public void setRunning(boolean b) {
running = b;
}
protected Canvas myCanvas;
protected Bitmap cvsBmp;
protected Matrix identityMatrix;
public void setSurfaceSize(int width, int height) {
synchronized (holder) {
mCanvasWidth = width;
mCanvasHeight = height;
}
}
}
What's happening is that if it's just ONE thread...it works fine. Once I introduce a second thread the mix...say a HUNDRED_BALLS_THREAD and a FIFTY_BALLS_THREAD that's when everything goes crazy.
The threading "works" if you want to call it that...but the screen flickers constantly.
I know the reasoning is probably obvious to some of you but unfortunately I don't understand why.
I would assume that because each thread is locking the canvas...it would wait.
Any way around this flickering? Is my design decision just completely wrong? I'm sure it's because each thread is accessing the same canvas...but I would think that would cause it flicker like that.