I'm experimenting with SurfaceView and creating simple animations with it, I don't understand why my animation (changins screen color from black to white) is working here (without using SurfaceHolder.Callback)
public class MySurface extends SurfaceView {
private boolean playing = true;
private int counter = 0;
public MySurface(Context context){
super(context);
new Anim().start();
}
private class Anim extends Thread {
#Override
public void run() {
while (playing) {
try {
sleep(1000);
draw();
counter++;
} catch (Exception e){
e.printStackTrace();
}
}
}
private void draw() {
SurfaceHolder holder = getHolder();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
if (counter % 2 == 0) {
canvas.drawColor(Color.WHITE);
} else
canvas.drawColor(Color.BLACK);
holder.unlockCanvasAndPost(canvas);
}
}
}
}
But it's not working here, what's the difference? I think it might be caused by calling .run() instead of .start()
public class MySurface extends SurfaceView implements SurfaceHolder.Callback {
Anim anim;
private boolean playing = true;
private int counter = 0;
public MySurface(Context context){
super(context);
}
// all Callbacks are overrided i didn add them to make code easier to read
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
anim = new Anim();
anim.run();
}
private class Anim extends Thread {
#Override
public void run() {
while (playing) {
try {
sleep(1000);
draw();
counter++;
} catch (Exception e){
e.printStackTrace();
}
}
}
private void draw() {
SurfaceHolder holder = getHolder();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
if (counter % 2 == 0) {
canvas.drawColor(Color.WHITE);
} else
canvas.drawColor(Color.BLACK);
holder.unlockCanvasAndPost(canvas);
}
}
}
}
EDIT: I forgot to add getHolder().addCallback(this) that caused the issue.
Could someone still point out the differnces between those two methods and tell which one is better?
EDIT2: anim.start() was also needed
Related
To preface, I'm probably going to work with Views instead of SurfaceView given this weird interaction, but my curiosity is getting the better of me and I want to know what's going on.
So I have a matrix class variable. I run matrix.setTranslate() once, and every other frame I say matrix.setTranslate(). After this I call canvas.drawBitmap(, matrix, null). After 100 frames, I stop drawing the bitmap.
Here's the code:
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// matrix = new Matrix(); //Doesn't matter if I add this.
// matrix.reset(); //This doesn't matter either.
if (!once) {
matrix.setTranslate(100, 100);
} else {
matrix.setTranslate(800,800);
}
once = true;
if (++timer < 100) {
canvas.drawBitmap(ball, matrix, null);
}
}
What I expect to happen: Three possibilities.
Only the bottom right bitmap is visible since the entire screen was invalidated
Both bitmaps are visible because SurfaceView was smart with the dirty rectangle
Nothing is shown because nothing was drawn.
What actually happens: The top left bitmap blinks, bottom right bitmap displays solidly.
I'm pretty sure I have all of my bases covered:
All class variables are properly set in the constructor.
SurfaceView.onDraw() is called every 33 millis in its own thread.
Thread calls lockCanvas, then onDraw, then unlockCanvasAndPost
So, what's going on here? And bonus question, do these ghost images consume any extra resources and how can I clear them?
Rest of the code, mostly boilerplate:
public class FullscreenActivity extends Activity {
GameSurface ball;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ball = new GameSurface(this);
setContentView(ball);
}
}
class GameSurface extends SurfaceView implements SurfaceHolder.Callback {
GameThread thread;
boolean once = false;
Bitmap ball;
Matrix matrix;
int timer = 0;
public GameSurface(Context context) {
super(context);
ball = BitmapFactory.decodeResource(getResources(),R.drawable.football);
matrix = new Matrix();
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// matrix = new Matrix(); //Doesn't matter if I add this.
// matrix.reset(); //This doesn't matter either.
if (!once) {
matrix.setTranslate(100, 100);
} else {
matrix.setTranslate(800,800);
}
//matrix.postRotate(timer); //For even more weirdness...
once = true;
if (++timer < 100) {
canvas.drawBitmap(ball, matrix, null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
class GameThread extends Thread {
private SurfaceHolder surfaceHolder;
private GameSurface gameView;
private boolean run = false;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameView) {
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
public void setRunning(boolean run) {
this.run = run;
}
#Override
public void run() {
Canvas c;
while (run) {
c = null;
try {
Thread.sleep(33);
}
catch(InterruptedException e) {}
try {
c = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
gameView.onDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
I have a Main Activity and in the corresponding XML Layout I have a Custom View that draws game objects (my tank and 10 enemies), a few buttons to control my tank and fire bullets and a TextView to show my score. My custom view is a GameSurfaceView java class that is a half screen game board.
Here is some of my code:
public class GameSurfaceView extends SurfaceView implements Runnable {
private static Context gContext;
public GameSurfaceView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
resume();
gContext = context;
}
public void resume() {
isRunning = true;
gameThread = new Thread(this);
gameThread.start();
}
public void pause() {
isRunning = false;
boolean retry = true;
while (retry) {
try {
gameThread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public void run() {
while (isRunning) {
// We need to make sure that the surface is ready
if (!holder.getSurface().isValid()) {
continue;
}
long started = System.currentTimeMillis();
// update
step();
// draw
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
render(canvas);
holder.unlockCanvasAndPost(canvas);
}
//detect all possible collisions
detectCollisions();
float deltaTime = (System.currentTimeMillis() - started);
int sleepTime = (int) (FRAME_PERIOD - deltaTime);
if (sleepTime > 0) {
try {
gameThread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
while (sleepTime < 0) {
step();
sleepTime += FRAME_PERIOD;
}
}
}
//Called from MainActivity
public void dispatchKey(int tDirection) {
Toast.makeText(gContext, "Hi", Toast.LENGTH_LONG).show();
gameStarted = true;
if (tDirection == FIRE)
Fire();
else if (tDirection != tank.Direction)
turnTankDirection = tDirection;
}
private void detectCollisions() {
//Collision Detection between tank and enemy
Toast.makeText(gContext, "Collision", Toast.LENGTH_LONG).show();
}
}
My questions:
1- Why the Toast in dispatchKey() runs correctly but Toast in detectCollisions() makes a force close?
2- How to update TextView in detectCollisions() method?
3- How to show a DialogAlert when a collision detected in detectCollisions() method?
My problem relates mainly to gContext variable.
Thanks.
Regarding question 1: Maybe this effects the second thread. While dispatchKey() is called from Activity, detectCollision() is invoked from the sureface-thread. Did you tried to call detectCollision() from activity?
Regarding question 3: let your activity implement a listener, which will be called if collision is detected. The same thing you could use as solution of question 1 and 2.
You should try:
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
//Make toast or manipulate TextView
}
});
public class CannonView extends SurfaceView implements SurfaceHolder.Callback {
CannonThread cannonThread;
private Paint blockerPaint;
public CannonView(Context context, AttributeSet attrs) {
super(context, attrs);
blockerPaint = new Paint();
blockerPaint.setStrokeWidth(10.0f);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
cannonThread = new CannonThread(holder);
cannonThread.running(true);
cannonThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
cannonThread.running(false);
while (retry) {
try {
cannonThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
private class CannonThread extends Thread {
boolean setRunning;
SurfaceHolder surfaceHolder;
public CannonThread(SurfaceHolder holder) {
setRunning = true;
surfaceHolder = holder;
}
public void running(boolean isRunning) {
setRunning = isRunning;
}
#Override
public void run() {
Canvas canvas = null;
while (setRunning) {
try {
canvas = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
canvas.drawLine(0, 0, 100, 100, blockerPaint);
}
} finally {
if (canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
Although above code is very simple
but it is not drawing anything on my activity ..
Logcat says i am doing too much work on main thread ..62 frame skipped ..
Please help
Well this isn't my style of coding, so I decided that you need to simplify things a bit. You used the android api guides but they suck.
Watch the following videos and you should be fine.
http://www.youtube.com/watch?v=wUmId0rwsBQ&list=SP2F07DBCDCC01493A&index=67
http://www.youtube.com/watch?v=0wy907WZFiA&list=SP2F07DBCDCC01493A
http://www.youtube.com/watch?v=ZMcYbf9Hhe4&list=SP2F07DBCDCC01493A
http://www.youtube.com/watch?v=yowNavIDzzE&list=SP2F07DBCDCC01493A
I'm new to programming in android and I can't understand why the OnDraw() method in my renderer doesn't get called as invalidate() is called. The final set background resource does get called however. There are 2 classes, Game.java and Renderer.java. It seems that the invalidate call does not have time to get processed during the loop in game.
Game.Java
public class Game extends Activity
{
Level currentLevel;
private static List<Entity> _renderList = new ArrayList<Entity>();
public void StartLevel(View view)
{
System.out.println("I HAVE STARTED THE LEVEL!");
// Hide the play button
Button playButton = (Button) findViewById(R.id.start_game);
playButton.setVisibility(View.GONE);
// Load the level
currentLevel = new Level1(this);
// Create the initial entities
// Add the entities to the screen
List<Bitmap> bitmapList;
bitmapList = new ArrayList<Bitmap>();
Bitmap enemy = BitmapFactory.decodeResource(getResources(), R.drawable.enemy);
bitmapList.add(enemy);
System.out.println("CREATING RENDERER!");
Renderer renderer = new Renderer(this, _renderList, bitmapList);
renderer.setBackgroundResource(R.drawable.beachbackground);
setContentView(renderer);
System.out.println("STARTING TRAVEL!");
while (currentLevel.GetDistTravelled() < currentLevel.GetDist())
{
Retrieve();
currentLevel.AddDistance(1);
System.out.println("DISTANCE +1!");
renderer.RenderFrame(_renderList);
System.out.println("RENDER LEVEL FRAME!");
try
{
Thread.currentThread().sleep(50);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("SET BACKGROUND!");
renderer.setBackgroundResource(R.drawable.menubackground);
}
public List<Entity> GetRenderList()
{
return _renderList;
}
private void Retrieve()
{
// get the array
Entity[] enemies;
enemies = currentLevel.CheckSpawn();
if(enemies != null)
{
// parse the array into entities
int length = enemies.length;
System.out.println(length);
// remember to Get enemy strings
for(int i = 0; i < length; i++)
{
// add them to our array
_renderList.add(enemies[i]);
}
}
}
}
Renderer.java
class Renderer extends SurfaceView implements SurfaceHolder.Callback
{
PanelThread _thread;
List<Entity> _renderList = new ArrayList<Entity>();
List<Bitmap> _bitmapList = new ArrayList<Bitmap>();
public Renderer(Context context, List<Entity> renderList, List<Bitmap> bitmapList)
{
super(context);
getHolder().addCallback(this);
_bitmapList = bitmapList;
}
public void RenderFrame(List<Entity> renderList)
{
for (int i = 0; i < renderList.size(); i++)
{
_renderList.add(renderList.get(i));
}
invalidate();
}
#Override
public void onDraw(Canvas canvas)
{
Paint paint = null;
// Draw enemies
if(_renderList.size() != 0 && _renderList != null)
{
int size = _renderList.size();
for(int i = 0; i < size; i++)
{
canvas.drawBitmap(_bitmapList.get(_renderList.get(i).GetBitmapID()), _renderList.get(i).GetX(), _renderList.get(i).GetY(), paint);
}
}
}
// position the train on the screen in the appropriate place
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()
_thread = new PanelThread(getHolder(), this); //Start the thread that
_thread.setRunning(true); //will make calls to
_thread.start(); //onDraw()
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
try
{
_thread.setRunning(false); //Tells thread to stop
_thread.join(); //Removes thread from mem.
}
catch (InterruptedException e) {}
}
class PanelThread extends Thread
{
private SurfaceHolder _surfaceHolder;
private Renderer _renderer;
private boolean _run = false;
public PanelThread(SurfaceHolder surfaceHolder, Renderer renderer)
{
_surfaceHolder = surfaceHolder;
_renderer = renderer;
}
public void setRunning(boolean run)
{
_run = run;
}
#Override
public void run()
{
Canvas c;
while (_run)
{
c = null;
try
{
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder)
{
//Insert methods to modify positions of items in onDraw()
postInvalidate();
}
}
finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
Any help on solving this problem would be very grateful. If anymore info is needed just ask.
Hi i have the same error like you. But i have found that. But i don't known clearly about that.
but you can try
add the attribute android:background to your layout.
I try to display one from list of bitmaps during onDraw.
When i'm passing list to the canvas all are display and stay in their places.
When I pass one random bitmaps it's redrawing canvas all the time.
All works when i'm using public void drawEnemy(Canvas canvas) but not exactly like I want when using public void drawEn(Canvas canvas).
I want to display one random bitmap, then after a few seconds, delete it and display other bitmap. I think the problem is how I implemented onDrow() method. It's redrawing canvas all the time.
Activity:
public class NewGameActivity extends Activity{
NewGame newgame;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// Landscape mode
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// no title
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// content Newgame.java
newgame = new NewGame(this);
setContentView(newgame);
}
Thread:
public class MainThread extends Thread{
private SurfaceHolder surfaceHolder;
private NewGame screen;
public MainThread(SurfaceHolder surfaceHolder, NewGame ekran) {
super();
this.surfaceHolder = surfaceHolder;
this.screen= screen;
}
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
Canvas canvas;
while (running) {
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
this.screen.onDraw(canvas);
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
SurfaceView:
public class NewGame extends SurfaceView implements SurfaceHolder.Callback{
private MainThread thread;
private EnemyManager manager;
public NewGame(Context context) {
super(context);
getHolder().addCallback(this);
thread = new MainThread(getHolder(), this);
manager = new EnemyManager();
// TODO Auto-generated constructor stub
//adding enemy
Enemy e1 = new Enemy(BitmapFactory.decodeResource(getResources(), R.drawable.card), 1);
Enemy e2 = new Enemy(BitmapFactory.decodeResource(getResources(), R.drawable.horse), 2);
EnemyLocation l1 = new EnemyLocation(60, 180);
EnemyLocation l2 = new EnemyLocation(60, 50);
manager.AddEnemy(e1, l1);
manager.AddEnemy(e2, l2);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.saloon), 0, 0, null);
manager.drawEn(canvas);
}
#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) {
thread.setRunning(false);
thread.stop();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
manager.handleActionDown((int)event.getX(), (int)event.getY());
}
return true;
}
}
EnemyManager:
public class EnemyManager {
private ArrayList<Enemy> enemyList;
private ArrayList<Enemy> suspects;
private Enemy cow;
private String message;
private int suspectID;
private Random rnd;
public String getMessage() {
return message;
}
public EnemyManager(){
enemyList = new ArrayList<Enemy>();
suspects = new ArrayList<Enemy>();
}
public void AddEnemy(Enemy enemy, EnemyLocation loc){
// set x,y enemy localization
enemy.setX(loc.getX());
enemy.setY(loc.getY());
enemyList.add(enemy);
}
public void clearEnemy() {
enemyList.clear();
}
// message if enemy touched
public void handleActionDown(int x, int y) {
for (Enemy enemy: enemyList) {
if (enemy.wasTouched(x, y)) {
message = enemy.getId();
return;
}
}
}
public void PrepareEnemy(){
suspectID = enemyList.get(rnd.nextInt(enemyList.size()+1)).getId();
suspects = new ArrayList<Enemy>();
suspects.add(getSuspectByID(suspectID));
}
private Enemy SingleEnemy(){
Double i = 1 + Math.random() * ((enemyList.size()-1)+1);
cow = getSuspectByID(i.intValue());
return cow;
}
private Enemy getSuspectByID(int suspectID) {
for (Enemy s: enemyList) {
if (s.getId() == suspectID) {
return s;
}
}
return null;
}
public void drawEn(Canvas canvas){
try {
Enemy k = SingleEnemy();
canvas.drawBitmap(cow.picture, cow.x, cow.y, null);
} catch (Exception e) {
// TODO: handle exception
}
}
// draw enemy
public void drawEnemy(Canvas canvas) {
try {
for (Enemy enemy: enemyList) {
canvas.drawBitmap(enemy.picture, enemy.x, enemy.y, null);
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
das
As for as understand you are trying to do something like this (if it's not, please correct me):
This is rendering the canvas with all components:
Draw background
Draw enemy
To "refresh" the canvas you simply do something like this:
Draw background
Update
To pause the rendering you could do something like this:
int lastUpdateTime;
int delayTime = 2000; 2 seconds
if(System.currenttimeMillis() > lastUpdateTime + delayTime) {
// Finished waiting
}
You should only define lastUpdateTime when you want to wait and not in every iteration.
NB: Don't call Thread.sleep() in a rendering thread!