android getting an error while trying to draw to the canvas - java

i have a class Handler that is used to draw an arraylist of towers in the gameview class
public class Handler {
public static int Scale=50;
public static ArrayList<Tower> towers=new ArrayList<Tower>();
public static void draw(Canvas c){
for(Tower t:towers){
t.draw(c);
}
}
public static void update(){
for(Tower t:towers){
t.update();
}
}
}
GameView class that extends surfaceview and implments surfaceholder.callback
public class GameView extends SurfaceView implements
SurfaceHolder.Callback {
public int ScreenWidth,ScreenHeight;
TouchInput touchinput;
TableLayout table;
///////////////////////////////////////
//these variables used for adding new tower
boolean addingnewtower=false;
Bitmap Tempbitmap;
public float x,y;
//////////////////////////////////////
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
table=(TableLayout) findViewById(R.id.tablelayout1);
Sprite sprite=new Sprite(context.getResources());
touchinput=new TouchInput(this);
Tempbitmap=sprite.bitmap;
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
setFocusable(true);//
}
private static final String TAG = GameView.class.getSimpleName();
private MainThread thread;
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
//if(!thread.isAlive()){
setOnTouchListener(touchinput);
thread = new MainThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
public void Draw(Canvas c) {
Paint paint=new Paint();//paint used to set color,text font,text size...
paint.setColor(Color.RED);
paint.setTextSize(paint.getTextSize()*3);
c.drawRect(100,100,200,200,paint);
c.drawText("touch is" + x + " "+ y,200,200, paint);
Handler.draw(c);
if(addingnewtower){
c.drawBitmap(Tempbitmap,x,y, paint);
}
}
public void update(){
Handler.update();
}
class mainthread which contains the game loop in the method run()
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private GameView gameview;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder,GameView gameview) {
super();
this.surfaceHolder = surfaceHolder;
this.gameview = gameview;
}
#Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing on the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
if(canvas!=null){
canvas.drawColor(Color.BLACK);
gameview.Draw(canvas);}
gameview.update();
}
} finally {
// in case of an exception the surface is not left in
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
and finally MainActivity that implements ontouchlistesnser
public class MainActivity extends Activity implements OnTouchListener {
private ImageView tower1,tower2,tower3,tower4,tower5,tower6;
private TableLayout layout;
private GameView gameview;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//hide the action bar
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//make it full screen
setContentView(R.layout.activity_main);
gameview=(GameView) findViewById(R.id.gameview1);
tower1=(ImageView) findViewById(R.id.tower1);
layout=(TableLayout) findViewById(R.id.tablelayout1);
tower1.setOnTouchListener(this);
gameview.setOnTouchListener(this);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
gameview.onpause();
}
#Override
protected void onResume() {
super.onResume();
gameview.onresume();
}
//this is called whenever an imageview is touched for adding a new tower on the map
#Override
public boolean onTouch(View v, MotionEvent m) {
int location[]=new int[2];
tower1.getLocationOnScreen(location);//get the location of imageview with respect to screen since the touch area will be with respect to the view v width and height
switch(m.getAction()){
case MotionEvent.ACTION_DOWN:
if(v.getId()==tower1.getId()){//if tower1 is touched
layout.setVisibility(View.INVISIBLE);//make them invisible
}
break;
case MotionEvent.ACTION_MOVE:
gameview.handle_imageView_TouchEvent(location,m.getX(),m.getY(),false);//false meaning in touch move
break;
case MotionEvent.ACTION_UP:
gameview.handle_imageView_TouchEvent(location,m.getX(),m.getY(),true);//this will be called only once for adding the new tower
layout.setVisibility(View.VISIBLE);
gameview.addingnewtower=false;
break;
}
return true;
}
}
when this code do is that whenever i touch an imageview and drag it across the sreen,a new object machinegunner which extend tower is added to the arraylist tower in the handler class
but after few times of adding new machinegunner to arraylist i am getting a error in the draw method of handler
01-20 18:16:13.261: E/AndroidRuntime(1339): FATAL EXCEPTION: Thread-114
01-20 18:16:13.261: E/AndroidRuntime(1339): java.util.ConcurrentModificationException
01-20 18:16:13.261: E/AndroidRuntime(1339): at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:569)
01-20 18:16:13.261: E/AndroidRuntime(1339): at com.example.test1.Handler.draw(Handler.java:15)
01-20 18:16:13.261: E/AndroidRuntime(1339): at com.example.test1.GameView.Draw(GameView.java:97)
01-20 18:16:13.261: E/AndroidRuntime(1339): at com.example.test1.MainThread.run(MainThread.java:44)
plz any help?

The problem appears to be that you are adding to the ArrayList whilst in the middle of iterating over it in the draw() method. You can't modify a collection whilst iterating over it (other than removing items using the Iterator).
You don't show what the method 'gameview.handle_imageView_TouchEvent()' does, but I think you need to make it add to the ArrayList using the same Handler that does the draw() and update(). The two processes should then queue up on the same thread and not collide.

Related

Android: Activity orientation is not always changed after screen rotation

I have a problem with a simple android application. It has a SurfaceView with simple drawing, but activity orientation sometimes seems not to be changed after a screen rotation.
This is how an activity looks in the portrait mode:
landscape mode:
but sometimes, when I rotate the screen, an activity looks like this in the portrait mode:
MainActivity.java:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback
{
private Thread mGameThread;
private GameApp mGameApp;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
SurfaceHolder holder = mainView.getHolder();
holder.addCallback(this);
mGameApp = new GameApp(getResources(), holder);
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
mGameThread = new Thread(new Runnable()
{
#Override
public void run()
{
mGameApp.run();
}
});
mGameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
mGameApp.setSurfaceSize(width, height);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
mGameApp.setRunning(false);
while (retry)
{
try
{
mGameThread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
}
GameApp.java:
package com.example.myapplication;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.view.SurfaceHolder;
public class GameApp
{
private Resources mResources;
private SurfaceHolder mSurfaceHolder;
private int mCanvasHeight = 1;
private int mCanvasWidth = 1;
private volatile boolean mRun = false;
public GameApp(Resources resources, SurfaceHolder surfaceHolder)
{
mResources = resources;
mSurfaceHolder = surfaceHolder;
}
public void setSurfaceSize(int width, int height)
{
synchronized (mSurfaceHolder)
{
mCanvasWidth = width;
mCanvasHeight = height;
}
}
public void run()
{
setRunning(true);
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun && canvas != null)
{
draw(canvas);
}
}
}
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public void setRunning(boolean b)
{
mRun = b;
}
private void draw(Canvas canvas)
{
canvas.drawColor(Color.GREEN);
Drawable cellImage = mResources.getDrawable(R.drawable.cell);
final float cellWidth = mCanvasWidth / 6;
final float cellHeight = mCanvasHeight / 6;
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
float x = i * cellWidth;
float y = j * cellHeight;
cellImage.setBounds(Math.round(x), Math.round(y), Math.round(x + cellWidth), Math.round(y + cellHeight));
cellImage.draw(canvas);
}
}
}
}
Eventual answer:
The problem is that you are doing a while loop in your GameApp's thread that locks the canvas and then unlocks without any long blocking or sleep in between.
The SurfaceHolder#lockCanvas documentation states:
If null is not returned, this function internally holds a lock until the corresponding unlockCanvasAndPost(Canvas) call, preventing SurfaceView from creating, destroying, or modifying the surface while it is being drawn.
So this means that the destroy code which is run from main thread needs to run between the unlockCanvasAndPost and the next lockCanvas. But since you have no sleep or even other code in between (except for the while condintion check), the chance of this happening is very small, and - depending on the device - could practically take forever.
To fix this, put a sleep in your game app to achieve the wanted framerate,
in it's most simple from this could look like this.
class GameApp
...
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun && canvas != null)
{
draw(canvas);
}
}
}
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
// Add some sleep time depending on how fast you want to refresh
try {
Thread.sleep(1000/60); //~60 fps
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Original answer 1: What could have helped in case of handling orientation changes
Seems like the surface is not always re-drawn on a config change.
Try overriding the onConfigurationChanged method of the activity and then triggering a re-layout of the surface
in MainActivity.java:
...
#Override
public void onConfigurationChanged(Configuration newConfig) {
// You should save (SurfaceView)findViewById(R.id.mainView) in a field for better performance, but I'm putting it here for shorter code.
SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
mainView.invalidate();
mainView.requestLayout();
}
...
More info on these methods nicely explained here:
Usage of forceLayout(), requestLayout() and invalidate()
Original answer 2: A way to check if your threads are blocked
Another possibility is that you have a thread lock issue on your main thread.
For checking that you could change your activity like this:
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private Thread mGameThread;
private GameApp mGameApp;
private static View currMainView;
private static Thread logPosterThread = new Thread(new Runnable() {
volatile boolean mainLogDone = true;
#Override
public void run() {
Runnable mainThreadLogger = new Runnable() {
#Override
public void run() {
Log.d("THREAD_CHECK", "Main Thread is ok");
mainLogDone = true;
}
};
while (true) {
try {
int sleepTime = 1000;
Thread.sleep(sleepTime);
if (currMainView != null) {
if (mainLogDone) {
mainLogDone = false;
Log.d("THREAD_CHECK", "Main Thread doing post");
currMainView.post(mainThreadLogger);
} else {
Log.w("THREAD_CHECK", "Main Thread did not run the runnable within " + sleepTime + "ms");
mainLogDone = true;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
static {
logPosterThread.start();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SurfaceView mainView = (SurfaceView) findViewById(R.id.mainView);
currMainView = mainView;
SurfaceHolder holder = mainView.getHolder();
holder.addCallback(this);
mGameApp = new GameApp(getResources(), holder);
}
#Override
public void onPause() {
...
And then see if those logs still get printed with 1000ms in between when the issue happens.
When you try this, you will see that actually the main thread is hanging when this happens.

OnTouch not working in Player class

I am creating a game and would like to read the player input for the character in the characters class. I don't know if this is good practice for game development however it seems like the logical thing to do.
I have the following code within my Player class (relevant code included) however my suspicion is that it isn't working as it isn't the primary view class or something? All help is appreciated.
Player
public class Player implements View.OnTouchListener{
private static Player instance = null;
int x, y;
boolean moveRight = false;
Bitmap sprite;
public Player (Context context){
sprite = BitmapFactory.decodeResource(context.getResources(), R.drawable.test_sprite);
x = 300;
y = 0;
}
public void Update(){
if (moveRight){
x += 3;
} else {
x -= 3;
}
}
public static Player getInstance(Context context) {
if (instance == null){
instance = new Player(context);
}
return instance;
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
float x = motionEvent.getX(), y = motionEvent.getY();
switch (motionEvent.getAction() & motionEvent.getActionMasked()){
case MotionEvent.ACTION_DOWN:
moveRight = true;
break;
case MotionEvent.ACTION_UP:
moveRight = false;
break;
}
return true;
}
}
GameView
public class GameView extends SurfaceView implements Runnable {
private boolean running = true;
SurfaceHolder surfaceHolder = getHolder();
SimpleTurret simpleTurret;
Player player;
private Thread thread = new Thread(this);
public GameView (Context context){
super(context);
simpleTurret = SimpleTurret.getInstance(context);
player = Player.getInstance(context);
thread.start();
}
#Override
public void run() {
while (running){
DrawCanvas();
Control();
}
}
public void DrawCanvas(){
Canvas canvas = surfaceHolder.lockCanvas();
if (surfaceHolder.getSurface().isValid()){
canvas.drawColor(Color.RED);
canvas.drawBitmap(simpleTurret.getSprite(), simpleTurret.getX(), simpleTurret.getY(), null);
canvas.drawBitmap(player.getSprite(), player.getX(), player.getY(), null);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
public void Control(){
try{
thread.sleep(17);
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class GameView extends SurfaceView implements Runnable, OnTouchListener {
public GameView(Context context) {
super(context);
this.setOnTouchListener(this);
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return false;
} ...
this.setOnTouchListener(this); line puts the OnTouchListener on the GameView class which extends the SurfaceView. So now anytime there is a touch on your SurfaceView the onTouch method would be called
You can use ImageView instead a Bitmap and set your implementation of View.OnTouchListener with the method setOnTouchListener:
ImageView sprite;
public Player (Context context){
sprite = new ImageView(context);
sprite.setBackgroundResource(R.drawable.monkey);
sprite.setOnTouchListener(this);
x = 300;
y = 0;
}

Invalidate in Thread Every X Seconds

So I have a thread that is instantiated inside a view. The thread should call postInvalidate() to redraw the screen every 16 milliseconds (60fps). The screen never redraws itself, though. It just remains blank. What am I doing wrong? Code is below:
public class DrawingView extends View {
private Paint paint;
private GraphicsLoop gThread;
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
gThread = new GraphicsLoop();
gThread.start();
}
#Override
protected void onDraw(Canvas canvas) {
// Draw Stuff
}
}
Thread:
public class GraphicsLoop extends Thread {
private long frameTime;
GraphicsLoop(){
}
#Override
public void run() {
while(true) {
long frameTimeDelta = System.currentTimeMillis() - frameTime;
if (frameTimeDelta > 16) {
frameTime = System.currentTimeMillis();
postInvalidate();
}
}
}
#Override
public void start ()
{
frameTime = System.currentTimeMillis();
}
}
You're missing a call to super.start() in the overriden start() method in GraphicsLoop, thus the thread is never started.

Android: boolean not working correclty - Strange

I was building a surfaceView class to display an image from the camera. It took me a while to get it working but I added some toasts, dialogs and some boolean variables to debug it in the AnnotateSurface class and the Activity class. I was using one of these in an if statement to run the thread to draw the image to the surface if it was true (this was what was stopping it from working).
if(bitmapState){
drawSomething(canvas);
}
I have got it working but only after I figured out that the boolean value, bitmapState , was being reset to false. it is initalised to false but I change it to true after in the getBitmap method. This is the only method that edits that boolean variable.
The toast in the setBitmap method displays "true", but when I call the getStuff method from the activity class it displays it as false and the if statement in the run method will also not work when I use the following:
if (surfaceState == bitmapState == true) {
drawSomething(canvas);
}
This is really confusing as all other boolean values are behaving correctly. If anyone has any insight I would love to hear it.
public class AnnotateSurface extends SurfaceView implements Callback, Runnable {
// create a holder to manage the surface of the view
SurfaceHolder ourHolder;
// Create a thread
Thread ourThread = null;
// Create a boolean to determine when to stop the animation
boolean isRunning = false;
boolean surfaceState = false;
boolean bitmapState = false;
public static Bitmap bmpIm = null;
Context c;
Canvas canvas;
int ws, hs;
public AnnotateSurface(Context context) {...
}
public AnnotateSurface(Context context, AttributeSet attrs) {...
}
public AnnotateSurface(Context context, AttributeSet attrs, int defStyle) {...
}
// create a method to pause the thread
public void pause() {...
}
public void resume() {...
}
// create a method to resume the thread
public void setBitmap(Bitmap b) {
bmpIm = b;
// bmpIm = AnnotateImage.bmpAnnotate;
// isRunning = true;
bitmapState = true;
Toast toast = new Toast(c);
Toast.makeText(c, Boolean.toString(bitmapState), Toast.LENGTH_LONG)
.show();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {...
}
#Override
public void surfaceCreated(SurfaceHolder holder) {...
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
surfaceState = false;
}
// #Override
public void drawSomething(Canvas canvas) {...
}
public String getstuff() {
String Data = "not drawing" + Boolean.toString(this.bitmapState)
+ Boolean.toString(surfaceState) + Boolean.toString(isRunning)
+ Integer.toString(ws) + Integer.toString(hs);
return Data;
}
public Bitmap getstuffImage() {...
}
#Override
public void run() {...
}
}
I have had problems with booleans and "if" statements in the past, the best way to deal with it is to be more explicit with them, for example:
if (surfaceState == bitmapState && bitmapState == true) {
drawSomething(canvas);
}
Cheers!
Mike

Drawable not displayed

Why mImage is not displayed?
public class Manager extends Thread{
private SurfaceHolder mSurfaceHolder;
private boolean mRunning;
public Drawable mImage;
public Manager(SurfaceHolder surfaceHolder, Context context){
mSurfaceHolder = surfaceHolder;
mRunning = false;
Resources res = context.getResources();
mImage = res.getDrawable(R.drawable.nhero2);
public void setRunning(boolean running)
{
mRunning = running;
}
public void run()
{
while (mRunning)
{
Canvas canvas = null;
try
{
// подготовка Canvas-а
canvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder)
{
// собственно рисование
//doDraw(canvas);
mImage.draw(canvas);
}
}
catch (Exception e) { }
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
P.S. sorry 4 my english
P.P.S.
i trying to create my own scrool shooter hah
full code here(sry for bad comments)
class Manager http://pastebin.com/Sjd57uqT
class View and class scrolBckgr http://pastebin.com/A5u5UJea
You need to call mImage.setBounds(...); otherwise the canvas doesn't know where to draw the Drawable.
I think the main problem you have is that as soon as you start the thread with .start(); the boolean mRunning is false and the thread will finish immediately. When you try to call .setRunning(true); after the thread has started it won't have any effect because the thread already finished.

Categories

Resources