1

I was trying to draw some balloons on screeen using canvas in SurfaceView class, I was successfully able to draw Multiple balloons on canvas and animate them by swapping different images. The problem is When I try touching on Oneballoon I need to remove it from screen .Here I get this exception and I am stuck

Code: MainActivity:

package com.pradhul.game.touchball;
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new GameView(this));
    /*TODO Hide the bottom navigation bar */
}

}

GameView.java

package com.pradhul.game.touchball;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GameView extends SurfaceView implements    SurfaceHolder.Callback, View.OnTouchListener {
private static final int NUM_OF_BALLOONS = 5; //TODO for more than one  balloons animations dont work(?)
/*use SurfaceView because we want complete control over the screen.
    * unlike extending View class the oDraw() Method will not be called automatically
    * from the method onSurfaceCreated() we have to call it Manually and pass a canvas object into it
    * */
private final SurfaceHolder holder;
private GameLoopThread gameLoopThread;
private List<Balloon> balloons =  new ArrayList<>();

public GameView(Context context) {
    super(context);
    gameLoopThread = new GameLoopThread(this);
    holder = getHolder();
    holder.addCallback(this);
    createBalloons(NUM_OF_BALLOONS);
    this.setOnTouchListener(this);
}

private void createBalloons(int count) {
    for(int i=0 ; i< count ;i++){
        balloons.add(createBalloon());
    }
}

@Override
protected void onDraw(Canvas canvas) {
    if(canvas != null) {
        canvas.drawColor(Color.WHITE);
        for(Balloon balloon : balloons){
            try {
                gameLoopThread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balloon.onDraw(canvas);
        }
    }
}

@SuppressLint("WrongCall")
@Override
public void surfaceCreated(SurfaceHolder holder) {
    /*this is called when the view is created*/
    gameLoopThread.setRunning(true);
    gameLoopThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    /*pausing game Thread*/
    gameLoopThread.setRunning(false);
    while (true){
        try {
            gameLoopThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
private  Balloon createBalloon(){
    return new Balloon(this);
}


@Override
public synchronized boolean onTouch(View v, MotionEvent event) {
    Log.d("OnTouch real -", "x: " + event.getX() + ", Y: " + event.getY());
 /*   for (int i = balloons.size()-1; i >= 0; i--) {
        Balloon balloon = balloons.get(i);
        Log.d("OnTouch collision -", !balloon.isCollision(event.getX(), event.getY())+"");
        if (!balloon.isCollision(event.getX(), event.getY())) {
            balloons.remove(0);
            break;
        }
    }*/
    Iterator<Balloon>  balloonIterator = balloons.iterator();
    while(balloonIterator.hasNext()){
        Balloon balloon = balloonIterator.next();
        balloons.remove(0);
    }
    return true;
}
}

GameLoopThread.java

package com.pradhul.game.touchball;
import android.annotation.SuppressLint;
import android.graphics.Canvas;

public class GameLoopThread extends Thread {

private GameView view;
private boolean running = false;

public GameLoopThread(GameView view){
    this.view = view;
}
public void setRunning(boolean run){
    running = run;
}

@SuppressLint("WrongCall")
public void run(){
    while (running){
        Canvas canvas = null;
        try{
            canvas = view.getHolder().lockCanvas();
            synchronized (view.getHolder()){
                view.onDraw(canvas);
            }
        }finally{
            if(canvas != null) {
                view.getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

}
}

Balloon.java

package com.pradhul.game.touchball;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.Log;
import java.util.Random;

public class Balloon {
private static final int BALLOON_SPEED = 10;
private int y = 0;
private int x = 0;
private int speed = 1;
private GameView gameView;
private Bitmap balloon;
public Bitmap[] normalBalloons;
private int balloonIndex = 0;

private int normalImages[] = {R.drawable.normal_01,R.drawable.normal_02,R.drawable.normal_03,
        R.drawable.normal_04,R.drawable.normal_05,R.drawable.normal_06,R.drawable.normal_07,
        R.drawable.normal_08,
};
private int crackingImages[] = {R.drawable.crack_01,R.drawable.crack_02,R.drawable.crack_03,
        R.drawable.crack_04, R.drawable.crack_05,R.drawable.crack_04,R.drawable.crack_03,
        R.drawable.crack_02
};
private boolean reverseSwap = false;


public Balloon(GameView gameView){
    this.gameView = gameView;
    normalBalloons = new Bitmap[8];
    setUpImages();
}

public  void onDraw(Canvas canvas){
    /*draws the balloon in canvas */
    animateBalloon();
    update(canvas.getWidth());
    canvas.drawBitmap(balloon, x, y, null);
}

public boolean isCollision(float x2, float y2) {
    return x2 > x && x2 < x + balloon.getWidth() && y2 > y && y2 < y + balloon.getHeight();
}

private int getRandomX(int maxVal) {
    Random rand = new Random();
    return rand.nextInt(maxVal);
}

private void animateBalloon() {
    /*Animates the balloon by swapping resource image at each call*/
    this.balloon = getBalloons();
    Log.d("Balloon",balloonIndex % normalBalloons.length + "");
}

private void update(int canvasWidth) {
    /*updates the y position for moving the balloon*/
    if (y <= 0){
        /*so that the balloon starts from bottom
        * gameView will return a height only after the View is ready
        * getting 0 in constructor of this class*/
        y = gameView.getHeight();
        /*x is assigned a random between the width od the canvas
        * so that the balloons will appear random positions from below*/
        x = getRandomX(canvasWidth - balloon.getWidth());
    }
    if (y > gameView.getHeight() - balloon.getHeight() - speed) {
        speed = -BALLOON_SPEED;
    }
    y = y + speed;
    Log.d("Balloon","Positions:"+x+","+y);
}

private Bitmap getBalloons() {
    if(balloonIndex == normalBalloons.length-1) {
        reverseSwap = true;
    }
    if(balloonIndex == 0){
        reverseSwap = false;
    }
    balloonIndex = reverseSwap?balloonIndex-1:balloonIndex+1;
    return normalBalloons[balloonIndex];
}

private void setUpImages() {
    /*setting up resources array*/
    for(int count =0; count < normalImages.length; count++){
        Bitmap balloon =   BitmapFactory.decodeResource(gameView.getResources(), normalImages[count]);
        normalBalloons[count] = balloon;
    }


}
}

I am confused that why it causes an error like this , Can anybody can take look at it and suggest me a solution, is this the right way to remove ?

please share any suggestion

Thanks

P-RAD
  • 1,293
  • 2
  • 15
  • 36

3 Answers3

1

It's the wrong way to remove.

If you iterate over a Collection / List, and want to remove the current element, you must use Iterator.remove() method.

In your case, simply call balloonIterator.remove() instead of balloons.remove(0)

Even simpler, since you want to remove all elements from the list - you should simply call balloons.clear() and remove the loop completely.

Sripathi Krishnan
  • 30,948
  • 4
  • 76
  • 83
0

This exception is due to concurrent modification of list balloons.

As soon as you touch surface view onDraw() gets invoked with onTouch(), in which you are working on list balloons.

I think you should add touch listener to balloon instead of GameView.

Priya Singhal
  • 1,261
  • 11
  • 16
  • But OnTouchListener interface is associated to View class, how can I inplement that in my custom class ? I was thinking that the GameView class extends the surfaceView so the view should take care of touch, I was not able to call setOnTouchListner in my class anyway – P-RAD Jul 20 '15 at 05:34
0

okay , I need to wrap all my code inside a synchronized holder inorder this to work (don't know much about it)

@Override
public boolean onTouch(View v, MotionEvent event) {
    Log.d("OnTouch","x:"+event.getX()+"Y:"+event.getY());
    synchronized (getHolder()){
        for (int i=0 ;i<balloons.size();i++){
            balloons.remove(0);
            break;
        }
    }
    return true;
}
P-RAD
  • 1,293
  • 2
  • 15
  • 36
  • 1
    the synchronized statement makes ensures thread safety, meaning the variables will not be randomly touched by different threads as they may be fired off/finished at different/concurrent times – kimchibooty Apr 21 '16 at 19:07