Android - drawing on canvas not working inside Runnable - android

This is my first time working with android, and I'm having some trouble drawing on a canvas repeatedly inside a thread. It seems that it only gets drawn once, although the thread is running which I made sure using a toast that popped up each time the thread executed (I've removed it because it was bothering me) but the thread is running and the code inside gets executed, it just sort of doesn't redraw...
public class GameActivity extends Activity implements Runnable{
ImageView gameView;
public int screenWidth, screenHeight, objectSize;
private static int FPS = 30;
private boolean rightPressed = false, leftPressed = false;
private Player player;
private Thread thread;
private Canvas canvas;
private Bitmap blankBitmap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenWidth = size.x;
screenHeight = size.y;
objectSize = screenWidth/8;
player = new Player(objectSize, screenWidth/2 - objectSize/2, screenHeight - (objectSize+10));
blankBitmap = Bitmap.createBitmap(screenWidth,screenHeight,Bitmap.Config.ARGB_8888);
canvas = new Canvas(blankBitmap);
gameView = new ImageView(this);
gameView.setImageBitmap(blankBitmap);
draw();
setContentView(gameView);
thread = new Thread(this);
thread.start();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
leftPressed = true;
if((int)event.getX()<=screenWidth/2){
leftPressed = true;
rightPressed = false;
Toast.makeText(this, "left", Toast.LENGTH_SHORT).show();
}
if((int)event.getX()>screenWidth/2){
rightPressed = true;
leftPressed = false;
Toast.makeText(this, "right", Toast.LENGTH_SHORT).show();
}
}
if(event.getAction() == MotionEvent.ACTION_UP) {
rightPressed = false;
leftPressed = false;
Toast.makeText(this, "released", Toast.LENGTH_SHORT).show();
}
return super.onTouchEvent(event);
}
public void draw(){
canvas.drawColor(Color.argb(255, 255, 255, 255));
player.draw(canvas);
}
#Override
public void run() {
while (!player.shouldDie()) {
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (!rightPressed && !leftPressed) player.setDx(0);
if(rightPressed) player.setDx(player.getSpeed());
if(leftPressed) player.setDx(-player.getSpeed());
player.update();
draw();
}
});
Thread.sleep(1000/FPS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
I also checked that the x of the player does actually change, and if I manually set the dx to like 100 it does actually move the first time it is executed, but not later even though the thread is still running
anyway, here's the Player.class
public class Player {
private int health, size, x, y;
private double dx, speed;
private Paint paint;
public Player(int size, int x, int y){
health = 3;
speed = 1;
dx = 0;
this.size = size;
this.x = x;
this.y = y;
paint = new Paint();
paint.setColor(Color.argb(255, 26, 128, 182));
}
public void draw(Canvas canvas){
canvas.drawRect(x, y, x+size, y+size, paint);
}
public void update(){
x += dx;
}
public void hurt(){
health--;
}
public boolean shouldDie(){
if(health<=0)return true;
return false;
}
public void posToast(Context context){
Toast.makeText(context, "X: " + x, Toast.LENGTH_SHORT).show();
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public Paint getPaint() {
return paint;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public double getDx() {
return dx;
}
public void setDx(double dx) {
this.dx = dx;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
If anyone sees anything wrong in here I'd appreciate some help as I'm really confused as to why this doesn't work, especially since this is my first time working with android.

Seems you forgot to call rePaint() method. In Android, you have something called inValidate() which does redraw your canvas. You can either add in player draw method or activity draw method. That should do the trick.

Related

How to generate bitmap one after another from y axis in android game

I am working on a small simple game in which the hurdles coming out from top and there is an static ball which can only move on x-axis.When the hurdles coming out from top the user have to move the ball to avoid the collision.
I am placing 3 moving hurdles at a time.but my problem is they are coming out together i.e all three hurdles have the same y-axis values.I want it to come out one by one with some specific distance.
How can i achieve this.
Here is my GamePanel Class:
public class GamePanel extends SurfaceView implements Runnable {
private Thread thread = null;
private Ball ball;
private SurfaceHolder surfaceHolder;
private Paint paint;
private Canvas canvas;
volatile boolean playing = true;
private int hurdleCount = 3;
private Hurdles[] hurdles;
private int screenX, screenY;
private Rect ball_detectCollision;
public GamePanel(Context context, final int screenX, final int screenY) {
super(context);
ball = new Ball(context, screenX, screenY);
surfaceHolder = getHolder();
this.screenX = screenX;
this.screenY = screenY;
paint = new Paint();
canvas = new Canvas();
hurdles = new Hurdles[hurdleCount];
for (int i = 0; i < hurdleCount; i++) {
hurdles[i] = new Hurdles(context, screenX, screenY);
}
ball_detectCollision = new Rect(ball.getBall_x(), ball.getBall_y(), ball.getBitmap().getWidth(), ball.getBitmap().getHeight());
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
System.out.println("Surface Created");
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
System.out.println("Surface Changed");
thread = new Thread(GamePanel.this);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
System.out.println("Surface Destroyed");
}
});
}
private void draw() {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.RED);
canvas.drawBitmap(ball.getBitmap(), updatedValue, ball.getBall_y(), paint);
ball.setBall_x(updatedValue);
ball_detectCollision.left = ball.getBall_x();
ball_detectCollision.top = screenY - ball.getBitmap().getHeight() - 260;
ball_detectCollision.right = ball.getBall_x() + ball.getBitmap().getWidth();
ball_detectCollision.bottom = screenY - ball.getBitmap().getHeight() - 260 + ball.getBitmap().getHeight();
for (int i = 0; i < hurdleCount; i++) {
canvas.drawBitmap(hurdles[i].getBitmap(), hurdles[i].getX(), hurdles[i].getY(), paint);
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
#Override
public void run() {
while (playing) {
update();
draw();
control();
}
}
private void update() {
for (int i = 0; i < hurdleCount; i++) {
hurdles[i].update();
if (Rect.intersects(getBall_detectCollision(), hurdles[i].getDetectCollision())) {
System.out.println("Collision Detected");
playing = false;
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
showGameOverMessage();
}
});
}
}
}
public void pause() {
playing = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
And this is my Hurdle Class
public class Hurdles {
private Bitmap bitmap;
private int x;
private int y;
private int speed = 20;
private int maxX;
private int minX;
private int maxY;
private int minY;
private Rect detectCollision;
public Hurdles(Context context, int screenX, int screenY) {
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.hurdle);
maxX = screenX - bitmap.getWidth();
maxY = screenY;
minX = 0;
minY = 0;
Random generator = new Random();
detectCollision = new Rect(x, y, bitmap.getWidth(), bitmap.getHeight());
x = generator.nextInt(maxX);
y = minY;
}
public void update() {
y += speed;
if (y > maxY - getBitmap().getHeight()) {
Random generator = new Random();
y = minY;
x = generator.nextInt(maxX);
}
detectCollision.left = x;
detectCollision.right = x + bitmap.getWidth();
detectCollision.top = y;
detectCollision.bottom = y + bitmap.getHeight();
}
If you want to add delay/gap between hurdles, you can do it in your GamePanel constructor like :
public GamePanel(Context context, final int screenX, final int screenY) {
super(context);
int gapBetweenHurdles = 100;
int gap = 0;
for (int i = 0; i < hurdleCount; i++) {
//(screenY - gap) will move your hurdle above the screen
hurdles[i] = new Hurdles(context, screenX, screenY - gap);
//increment the gap
gap += gapBetweenHurdles;
}
......
}
So the gap between the hurdles is 100 pixels as i have written randomly. If you want specific gap, you can set gapBetweenHurdles to some percentage of the screen height.
EDIT:
You have to pass the initial X and Y position to the hurdle constructor and then in update method of the hurdle class increment the Y value and in Hurdle class, getY() should return Y.
Try changing the code like this:
handler.postDelayed(new Runnable() {
#Override
public void run() {
showGameOverMessage();
}
},timeOffsetInMillis);
If the gaps can be the same you can set timeOffsetInMillis = i * gapInMillis

Infinite background animation

I made an infinite background for an Android 2D game but the background scrolls only one time and doesn't loop. How can I fix this?
Another question: How do I make this same background fall from the top to bottom? For a game
.
This is my class (code):
public class GameView extends View implements Runnable {
private static final int INTERVAL = 10;
private boolean running = true;
private int y;
private Paint paint;
private int z = 0;
int sx;
Bitmap background;
public GameView(Context context){
super(context);
paint = new Paint();
background = BitmapFactory.decodeResource(getResources(), R.drawable.back);
Thread myThread = new Thread(this);
myThread.setPriority(Thread.MIN_PRIORITY);
myThread.start();
}
#Override
public void run() {
while (running) {
try {
Thread.sleep(INTERVAL);
} catch (InterruptedException e) {
Log.e("Game", "Gameloop finished!");
}
update();
}
}
private void update() {
if (y < getHeight()) {
y += 5;
} else {
y = 0;
}
postInvalidate();
}
public void draw(Canvas canvas) {
super.draw(canvas);
z = z-10;
if(z == -sx) {
z = 0;
canvas.drawBitmap(background, z, 0, null);
} else {
canvas.drawBitmap(background, z, 0, null);
}
}
public void release() {
running = false;
}
}

How to change attributes of the second Activity from Main activity?

In the main activity I have a handler to exchange messages with the bluetooth service. This works fine.
Now, I want launch second activity (SurfaceViewAnimation). I do this with:
startActivity (new Intent (this, SurfaceViewAnimation.class));
but I want change some attributes of SufaceViewAnimation class from main activity when it receives a command for bluetooth.
How I can do this?
The code of SufaceViewAnimation class is:
class BouncingBallView extends SurfaceView implements SurfaceHolder.Callback {
private BouncingBallAnimationThread bbThread = null;
private int xPosition = getWidth()/2;
private int yPosition = getHeight()/2;
private int xDirection = 20;
private int yDirection = 40;
private static int radius = 20;
private static int ballColor = Color.RED;
public BouncingBallView(Context ctx, AttributeSet attrs, int defStyle) {
super(ctx, attrs, defStyle);
getHolder().addCallback(this);
}
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawRect(0,0,getWidth(),getHeight(), paint);
paint.setColor(ballColor);
canvas.drawCircle(xPosition, yPosition, radius, paint);
}
public void surfaceCreated(SurfaceHolder holder) {
if (bbThread!=null) return;
bbThread = new BouncingBallAnimationThread(getHolder());
bbThread.start();
}
public void surfaceChanged(SurfaceHolder holder,int format, int width, int height) { }
public void surfaceDestroyed(SurfaceHolder holder) {
bbThread.stop = true;
}
private class BouncingBallAnimationThread extends Thread {
public boolean stop = false;
private SurfaceHolder surfaceHolder;
public BouncingBallAnimationThread(SurfaceHolder surfaceHolder) {
this.surfaceHolder = surfaceHolder;
}
public void run() {
while (!stop) {
xPosition += xDirection;
yPosition += yDirection;
if (xPosition<0) {
xDirection = -xDirection;
xPosition = radius; }
if (xPosition>getWidth()-radius) {
xDirection = -xDirection;
xPosition = getWidth()-radius; }
if (yPosition<0) {
yDirection = -yDirection;
yPosition = radius; }
if (yPosition>getHeight()-radius) {
yDirection = -yDirection;
yPosition = getHeight()-radius-1; }
Canvas c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(c);
}
} finally {
if (c != null) surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN) return false;
if (xDirection!=0 || yDirection!=0)
xDirection = yDirection = 0;
else {
xDirection = (int) event.getX() - xPosition;
yDirection = (int) event.getY() - yPosition;
}
if(ballColor==Color.RED)
ballColor=Color.GREEN;
else
ballColor=Color.RED;
return true;
}
public void setxDirection(int xDirection) {
this.xDirection = xDirection;
}
public void setyDirection(int yDirection) {
this.yDirection = yDirection;
}
public void setballColor(int ballColor) {
this.ballColor = ballColor;
}
public void setradius(int radius) {
this.radius = radius;
}
}
public class SurfaceViewAnimation extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new BouncingBallView(this,null,0));
}
}
If you want to change the data in SurfaceViewAnimation while SurfaceViewAnimation is running, you're best off with creating an own handler.
Besides that you can fall back to basic Inter(Process/Activity)Communication using Handlers, static variables or the Observer pattern.

Android SurfaceView with touch events

I have custom SurfaceView that containt a circle. I want to rotate this circle via touch events. Everything good except I can't pause run() method of SurfaveView when touch finished.
Here my view and thread
public class CircleSurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private Circle mCircle;
private DrawThread thread;
private float mRotation = 0;
long mLastMillis = 0L;
Paint clearPaint = new Paint();
public CircleSurfaceView(Context context) {
super(context);
mCircle = new Circle(0, 200, 200);
getHolder().addCallback(this);
this.setZOrderOnTop(true);
this.getHolder().setFormat(PixelFormat.TRANSPARENT);
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void startDrawing() {
if (thread == null) {
thread = new DrawThread(this);
thread.startThread();
}
}
public void stopDrawing() {
if (thread != null) {
thread.stopThread();
// Waiting for the thread to die by calling thread.join,
// repeatedly if necessary
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
thread = null;
}
}
#Override
protected void onDraw(final Canvas canvas) {
canvas.drawPaint(clearPaint);
mCircle.init();
mCircle.draw(canvas);
super.onDraw(canvas);
}
float degrees;
public void rotateCircle() {
mCircle.rotate(mRotation);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float currentX = event.getX();
float currentY = event.getY();
float deltaX, deltaY;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// Modify rotational angles according to movement
deltaX = currentX - previousX;
deltaY = currentY - previousY;
mRotation += deltaX + deltaY;
}
// Save current x, y
previousX = currentX;
previousY = currentY;
rotateCircle();
return true; // Event handled
}
private float previousX;
private float previousY;
private static int SEG_COUNT = 62;
private static float SEG_IN_GRAD = (float) 360 / SEG_COUNT;
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
startDrawing();
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
stopDrawing();
}
public class DrawThread extends Thread {
private boolean mRun = false;
private SurfaceHolder surfaceHolder = null;
private CircleSurfaceView circleSurfaceView = null;
public DrawThread(CircleSurfaceView circleSurfaceView) {
this.circleSurfaceView = circleSurfaceView;
this.surfaceHolder = circleSurfaceView.getHolder();
}
public void startThread() {
if (mRun == false) {
mRun = true;
super.start();
}
}
public void stopThread() {
if (mRun == true) {
mRun = false;
}
}
#Override
public void run() {
Canvas c = null;
while (mRun) {
c = null;
try {
c = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
if (c != null) {
Log.i("run", "run");
circleSurfaceView.rotateCircle();
circleSurfaceView.onDraw(c);
}
}
} finally {
// do this in a finally so that if an exception is thrown
// we don't leave the Surface in an inconsistent state
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}

Surfaceview flickering

I have a problem with the flickering.
Here is my code.
public class Tutorial2D3 extends Activity {
Panel panel;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
panel = new Panel(this);
setContentView(panel);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(1, 1, 1, "Clean Canvas");
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
panel.cleanCanvas();
return true;
}
class Panel extends SurfaceView implements SurfaceHolder.Callback {
TutorialThread thread;
Bitmap icon;
int iconWidth;
int iconHeight;
int touchX;
int touchY;
int mCount = 0;
public Panel(Context context) {
super(context);
icon = BitmapFactory
.decodeResource(getResources(), R.drawable.icon);
iconWidth = icon.getWidth();
iconHeight = icon.getHeight();
getHolder().addCallback(this);
thread = new TutorialThread(getHolder(), this);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
int x = touchX - (iconWidth / 2);
int y = touchY - (iconHeight / 2);
if(mCount>0) {
canvas.drawColor(Color.BLACK);
mCount--;
}
canvas.drawBitmap(icon, (x > 0 ? x : 0), (y > 0 ? y : 0), null);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
do {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (retry);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchX = (int) event.getX();
touchY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
touchX = (int) event.getX();
touchY = (int) event.getY();
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
private void cleanCanvas() {
mCount = 2;
}
}
class TutorialThread extends Thread {
private SurfaceHolder _surfaceHolder;
private Panel _panel;
private boolean _run = false;
public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.onDraw(c);
}
} finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
The drawn image flickers.
It looks like the bitmap that is drawn at one point is drawn on one surface and not the other so it looks like flickering, the bitmap that is drawn when we touch action_up is done, that is a solid image and does not flickers. Could someone please help me with this one.
Thanks
When you are drawing in the Canvas of a SurfaceView, you must always draw every pixel of the surface.
Here you are not always clearing the Canvas in onDraw(), hence the flickering.
One thing you could do to mitigate that (and to kinda contradict Guillaume :)) is to use surfaceholder.lockCanvas(rectangle), where it is only the specified rectangle part of the canvas which is then drawn (but you must draw every pixel of that rect). Here it is, ripped from the LunarLandar sample:
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(Rectangle);
synchronized (mSurfaceHolder) {
if (mMode == STATE_RUNNING) updatePhysics();
doDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
I didn't read through all of your code, but I think this article will help.
The essence of the article is that flickering is due to double buffering and can be eliminated by drawing not to the argument Canvas but to a bitmap used as the canvas and then drawing that bitmap to the arg Canvas:
int myCanvas_w, myCanvas_h;
Bitmap myCanvasBitmap = null;
Canvas myCanvas = null;
Matrix identityMatrix;
#Override
public void surfaceCreated(SurfaceHolder holder) {
myCanvas_w = getWidth();
myCanvas_h = getHeight();
myCanvasBitmap = Bitmap.createBitmap(myCanvas_w, myCanvas_h, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas();
myCanvas.setBitmap(myCanvasBitmap);
identityMatrix = new Matrix();
}
#Override
protected void onDraw(Canvas canvas) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
//int w = myCanvas.getWidth();
//int h = myCanvas.getHeight();
int x = random.nextInt(myCanvas_w-1);
int y = random.nextInt(myCanvas_h-1);
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
paint.setColor(0xff000000 + (r << 16) + (g << 8) + b);
myCanvas.drawPoint(x, y, paint); // <--------- Here's where you draw on your bitmap
canvas.drawBitmap(myCanvasBitmap, identityMatrix, null);
// ^---------- And here's where you draw that bitmap to the canvas
}

Categories

Resources