I am trying to put an image on the screen and change it every five seconds. This is not in an Activity class. Sorry if is a stupid question. This is a huge learning curve for me.
public class Panel extends SurfaceView implements SurfaceHolder.Callback {
private Handler mHandler = new Handler();
//stuff
public void doDraw(Canvas canvas) {
int counter = 0;
canvas.drawColor(Color.BLACK);
dot1.doDraw(getResources(), canvas, counter);
mHandler.removeCallbacks(panelDraw);
mHandler.postDelayed(panelDraw, 5000);
}
private Runnable panelDraw = new Runnable() {
public void run() {
}
};
(Panel->)mPanel.doDraw(canvas) in another class calls the doDraw listed in Panel. This is in a loop.
As far as I can tell, the first image isn't stopping for five seconds, and due to other code the cat pic flashes all over the screen. Can I get away with putting nothing in run()? I wanted to put the dot1.doDraw(getResources(), canvas, counter) one in there, but I couldn't/didn't think I could give void run() parameters or Runnable panelDraw resources, canvas, counter parameters.
Any help is appreciated. Let me know if I need to give more code.
A little more about my game: Kitten photos should appear one at random locations on the screen. If one does not click on the current kitty image with an allotted time, the game ends. These pictures last < 1 sec in the end (right now it's more for testing purposes). If you don't click the kitty in that time span, the game ends.
I heard that if you need to do a lot of drawing, you're better off using SurfaceView like in the tutorial I followed very closely: http://www.droidnova.com/2d-tutorial-series-part-v,848.html My Activity class (Scene1) has setContentView(new Panel(this)), so I don't know how to put stuff in the Activity class when it all goes in the Panel class. Like where do I put the ImageView code if the Activity thread doesn't really do much and hands it all off to Panel? Also if I have a ViewThread (public class ViewThread extends Thread) that handles the running:
public void run() {
Canvas canvas = null;
while (mRun1) {
do {
canvas = mHolder.lockCanvas();
if (canvas != null) {
mPanel.doDraw(canvas);
mRun1 = true;
mRun1 = mPanel.get_run();
mHolder.unlockCanvasAndPost(canvas);
}
} while (mRun1 == true);
The dot class has something like:
public void firstRing(Resources res, Canvas canvas, int counter){
Random rand = new Random();
switch(counter) {
case 0: mBitmap = BitmapFactory.decodeResource(res,
R.drawable.cat1_m);
break;
case 1: mBitmap = BitmapFactory.decodeResource(res,
R.drawable.cat2_m);
break;
case 2: mBitmap = BitmapFactory.decodeResource(res,
R.drawable.cat2_m);
break;
case 3: mBitmap = BitmapFactory.decodeResource(res,
R.drawable.cat2_m);
break;
case 4: mBitmap = BitmapFactory.decodeResource(res,
R.drawable.cat2_m);
break;
case 5: mBitmap = BitmapFactory.decodeResource(res,
R.drawable.cat1_m);
break;
}
mX = rand.nextInt((int)(Panel.mWidth - mBitmap.getWidth()));
mY = rand.nextInt((int)(Panel.mHeight - mBitmap.getHeight()));
canvas.drawBitmap(mBitmap, mX, mY, null);
and
public void doDraw(Resources res, Canvas canvas, int counter) {
firstRing(res, canvas, counter);
}
Thanks for the info, Barry, although I am a little confused. Do I try ImageView or do I stick with Handler(), although I don't know how to use it and it's not working now? Do I need something in public void run()?
This code should work better than my last answer, but I haven't compiled or tested it. Even if it doesn't work it will point you in the right direction. I assume you have an Activity subclass somewhere so just copy my code into your activity. Your layout should have an <ImageView> tag in it somewhere.
The code below creates a Handler in Activity.onCreate(), which is run in the UI thread, guaranteeing all calls to the Handler will also be run in the UI thread. It then calls showNextKittyImage() to display the first image. showNextKittyImage() sets the Drawable resource id for the ImageView, and then calls Handler.sendEmptyMessageDelayed() to call the handler 5 seconds later. The Handler calls showNextKittyImage() and the cycle repeats.
An ImageView is still preferable to a SurfaceView because it is designed to display images. All you need to do is pass it the Drawable resource id instead of messing with Bitmaps. If you really want or need to do it with a SurfaceView then I cannot help you.
This code will also not make the kittens appear at random locations. Get it working in one location first, then add code to move the ImageView around the screen randomly. If you need help at that point I strongly recommend you post a new question as you've asked A LOT for one posting.
Good luck,
Barry
public class KittyActivity extends Activity {
Handler mHandler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate your view
setContentView(R.layout.YOUR_LAYOUT);
// Create a handler ON THE UI THREAD
mHandler = new Handler(Message result) {
public void handleMessage(Message result) {
showNextKittyImage();
}
};
// Show the first kitty
showNextKittyImage();
}
public void showNextKittyImage() {
int kitty = getNextKitty();
ImageView iv = findViewById(R.id.YOUR_IMAGE_VIEW_ID);
iv.setImageResource(kitty);
// OPTIONAL: Move the ImageView to a new random location
mHandler.sendEmptyMessageDelayed(0, 5000);
}
private int getNextKitty() {
// Your code to get the next kitty drawable id
}
}
Related
I've been building a game for some time, and just realized I did something very wrong the whole time, and still don't really know better.
I have a control class, my MainActivity, which pretty much only does the following:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
operator=new Operator(getBaseContext());
setContentView(R.layout.activity_main); //<- framelayout with the view and two buttons on top
}
//Buttons:
public void shoot(View view) {
operator.shoot(view.getId());
}
public void pause(View view) {
AndronXView.running=!AndronXView.running;
}
Then there is my View, which draws Actors and makes my workerthread compute everything:
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
animHandler = new Handler();
animHandler.postDelayed(loadLvl1, 5000);
arrayOfActors = operator.getActors(); //simplyfied to one array
if(arrayOfActors==null)
arrayOfActors=new Actor[0]; //if op hasn't loaded yet, prevent NPE in onDraw
//stripped of unimportant color and size computing stuff
}
protected void onDraw(final Canvas canvas){
//Should I copy player before doing this? Never got problems here so far.
canvas.drawBitmap(operator.player.getCurrentGraphic(), operator.player.getX(), operator.player.getY(), null);
for(Actor actor:arrayOfActors) {
canvas.drawBitmap(actor.getCurrentGraphic(), actor.getX(), actor.getY(), null);
}
if (running) {
operator.run(); //Compute. Realized my mistake here and changed the inside, wait for it.
animHandler.postDelayed(r, Operator.FRAME_RATE); //r=invalidate();
animHandler.post(loadActors); //get Arrays from op
}else animHandler.post(wait);
}
Runnable wait = new Runnable() {
#Override
public void run() {
if (running)
animHandler.post(r);
else animHandler.postDelayed(this, Operator.FRAME_RATE);
}
};
#Override
public boolean onTouchEvent(#NonNull MotionEvent event){
int action = event.getAction();
if(action==MotionEvent.ACTION_DOWN || action==MotionEvent.ACTION_MOVE){
operator.player.setSpeed((event.getX()-operator.player.getHorizontalMid())
/AndronX.PLAYER_MOVE_LAG,
(event.getY()-operator.player.getVerticalMid())/AndronX.PLAYER_MOVE_LAG);
}
return true;
}
And then there is my Operator, extending Thread, which computes movement and interactions of the actors in the background:
public Operator(Context baseContext) {
this.player = new Player(baseContext); //context to load drawable from ressource
arrayListOfActors=new ArrayList<>();
//Looper.prepare(); //it crashed and said only one Looper/Thread
//Looper.loop(); //so I removed it
opHandler = new Handler();
}
#Override
public void run() {
opHandler.post(gameStep); //before, I had the whole processing inside this run().
}
private Runnable gameStep = new Runnable(){
player.move();
computeEveryMemberOf(arrayListOfActors); //much computing happens here, usually
//contains 1-30 Actors that get cross-referenced
arrayOfActors = arrayListOfActors.toArray(new Actor[arrayListOfActors.size()]);
}
public Actor[] getActors(){
return arrayOfActors;
}
Before, I had the computign directly in my operators run() method, which I realized made the background thread useless. I#m not sure though if this is the right way, should I let the operator loop itself, would the two threads kinda stay in sync? Does it even matter?
Or should I go run(){ sleep(FRAME_RATE); compute();}?
Edit: A big problem arose, and I'm not sure if it's because of this, so I really need an answer here how to do this the right way.
With every step, I move some actors a little bit up or down, in a cosinus wave like speed (like it's projection to an axis), and currently, the actual movement doesn't get through to the view, they just jump from max to min and back, although they do it in the desired speed(looks like extreme lag).
Actually you can see for yourself what this problem is: https://dl.dropboxusercontent.com/u/28278772/AndronX.apk
I'm writing a simple Whack a Mole clone, and I've got my UI elements declared in a GridLayout in a layout.xml, then assigned to ImageView variables in an array programmatically. I've got a startGame() method that simply takes a random int, pulls it from the array and causes it to go visible for a second, then repeats. For some reason, when I put this code in a while() loop, it causes my UI to go blank as soon as it's launched.
I know it's the while() loop because I tried taking the code out of the while() loop, and it ran correctly (once), but turns everything white when placed in a while loop.
Here's the method causing the problem:
public void startGame() {
gameStarted = true;
while(gameStarted) {
randomInt = rand.nextInt(11);
mole[randomInt].setVisibility(View.VISIBLE);
handler.postDelayed(new Runnable() {
#Override
public void run() {
mole[randomInt].setVisibility(View.INVISIBLE);
}
}, 5000);
}
}
All the other relevant code is in onCreate, it's otherwise just a skeleton Activity subclass.
public class WAM_Activity extends Activity {
private ImageView[] mole = new ImageView[11];
private int[] moleId = {R.id.mole1, R.id.mole3, R.id.mole4, R.id.mole5, R.id.mole6, R.id.mole7, R.id.mole8, R.id.mole9, R.id.mole10, R.id.mole11, R.id.mole12};
private boolean gameStarted;
private int randomInt = 0;
private Random rand = new Random();
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wam_view_layout);
for (int i = 0; i < 11; i++) {
mole[i] = (ImageView) findViewById(moleId[i]);
mole[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//do stuff eventually
}
});
}
gameStarted = true;
startGame();
}
Any idea why this isn't working? I've been staring at it for hours and I'm quite stumped.
Android doesn't work that way, when onCreate is called, it need to be finished in order for the app to keep responding, I'm surprised you are not getting any "App not respopnding" error.
If you want to create a "game loop" you can simply by creating a new Thread and putting the while in there.
Activity's lifecycle must be executed without blocking them for the app to operate correctly, for more info check here.
Do you know about threads? if you want i can post an example of how to do that with threads but it might be long and if you don't know what a Thread is it will be too confusing for you.
Edit: Ok I'll make an example of a Thread
When I create my games I usually have only one Activity that the only thing it does is creating a custom SurfaceView and nothing else.
public class GameActivity extends Activity
{
//This is a custom class that extends SurfaceView - I will write it below
private GameSurface game;
#Override
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
//Create a new instance of the game
game = new GameSurface(this);
//Set the View of the game to the activity
setContentView(game);
}
}
You can also add extra stuff like onSaveInstanceState to save game data and restore them later but I don't want to add them now so the code looks simple.
This class was very simple, let's move on to our SurfaceView. The reason I picked a SurfaceView to do that it's because it is made to allow custom graphics to be drawn on it - exactly what we want on a video game. I will try to make the class as simple as possible:
/*SurfaceHolder.Callback will run some functions in our class when
our surface is completed - at that point we can initialize data
that have to do with the View's width/height.
I don't know if you've noticed that on a View's onCreate()
when you call getWidth() or getHeight() you get 0, that's because
the surface is not initialized yet, this is a way to fix that.
Also we need a Runnable to run the Thread inside this class,
no need to make more classes and make it more complicated*/
public class GameSurface extends SurfaceView
implements SurfaceHolder.Callback, Runnable
{
//This is our thread - we need the "running" variable to be
//able to stop the Thread manually, this will go inside our "while" loop
private Thread thread;
private boolean running;
//Right here you can add more variables that draw graphics
//For example you can create a new class that has a function that
//takes Canvas as a parameter and draws stuff into it, I will add
//a Rect in this case which is a class already made by android
//but you can create your own class that draws images or more
//complicated stuff
private Rect myRect;
//Rect needs a paint to give it color
private Paint myPaint;
//Constructor
public GameSurface(Context context)
{
super(context);
//This is the callback to let us know when surface is completed
getHolder().addCallback(this);
}
//When a class implements SurfaceHolder.Callback you are forced to
//create three functions "surfaceCreated", "surfaceChanged" and
//"surfaceDestroyed" these are called when the surface is created,
//when some settings are changed (like the orientation) and when
//it is about to be destroyed
#Override
public void surfaceCreated(Surface holder)
{
//Let's initialize our Rect, lets assume we want it to have 40
//pixels height and fill the screen's width
myRect = new Rect(0, 0, getWidth(), 40);
//Give color to the rect
myPaint = new Paint();
myPaint.setARGB(0, 255, 0, 0);
//In case you are not familiar with the Rect class, as
//parameters it gets Rect(left, top, right, bottom)
//Time to start our Thread - nothing much to explain here if
//you know how threads work, remember this class implements
//Runnable so the Thread's constructor gets "this" as parameter
running = true;
thread = new Thread(this);
thread.start();
}
//We won't use this one for now, but we are forced to type it
//Even if we leave it empty
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
//When the surface is destroyed we just want the Thread to
//terminate - we don't want threads running when our app is not visible!
#Override
public void surfaceDestroyed(SurfaceHolder holder)
//We will type this function later
{destroyThread();}
//Time for the interesting stuff! let's start with input
#Override
public boolean onTouchEvent(MotionEvent event)
{
//The logic is as follows: when our Rect is touched, we want
//it to become smaller
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (myRect.contains((int) event.getX(), (int) event.getY())
{
myRect.right -= 5;
//Return true - we did something with the input
return true;
}
}
return super.onTouchEvent(event);
}
//This is our update, it will run once per frame
private void update()
{
//Let's assume we want our rect to move 1 pixel downwards
//on every frame
myRect.offset(0, 1);
}
//Now for our draw function
public void draw(Canvas canvas)
{
//Here we want to draw a background and our rect
canvas.drawARGB(0, 0, 0, 255);
canvas.drawRect(myRect, myPaint);
}
//The only thing left is our run() function for the Thread
#Override
public void run()
{
//Screen
Canvas canvas;
//Our game cycle (the famous while)
while(running)
{
//Count start time so we can calculate frames
int startTime = System.currentTimeMillis();
//Update our game
update();
//Empty screen so it can obtain new instance
canvas = null;
//Try locking the canvas for pixel editing on surface
try
{
//Try getting screen
canvas = getHolder().lockCanvas();
//Succeeded
if (canvas != null) synchronized (getHolder())
{
//Actual drawing - our draw function
draw(canvas);
}
} finally
{
//Draw changes
if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
}
//End Frame - 1000/30 means 30 frames per second
int frameTime = System.currentTimeMillis() -startTime;
if (frameTime < 1000/30)
try { Thread.sleep(1000/30 -frameTime); } catch (InterruptedException e){}
}
}
//Last but not least, our function for closing the thread
private void destroyThread()
{
//Stop thread's loop
running = false;
//Try to join thread with UI thread
boolean retry = true;
while (retry)
{
try {thread.join(); retry = false;}
catch (InterruptedException e) {}
}
}
}
I may have made some minor mistakes (probably with case sensitive letters) so feel free to correct these, I wrote the code at once so I didn't have time to test it, it should work flawlessly though.
If you have any more questions, need more explanation or something is not working right let me know!
I haven't been able to find a question online similar to this, so I thought I would submit the question. In most cases it seems people have the opposite problem were lag may be occurring during a touch event, but I am seeing the exact opposite. I am creating an air hockey game and each frame the pieces are moved based on their current parameters, and the game is redrawn while in the background the override ontouch listener is looking for motion events. The thing is there is noticeable lag when there is no fingers touching the screen and just watching the animated pieces, but as long as there is any form of motion event happening, if ontouch is being called, then the animation is smooth. I really can not figure why this is, my best educated guess is the interrupt that is checking if ontouch should be called is consuming more resources than it should, but I know nothing about how to modify or check the ontouch interrupt behavior at all so hopefully someone might know.
A little more background on how everything is organized. The overall class is located in Main.java, it is created here and requests the Context view, the game class is an extension of ImageView. The game class has a #override OnTouchEvent, which has all the defintions for what to do depending on what state and what motion event. The class also has a draw method, and at the end of the method it calls h.postDelayed(r, FRAME_RATE); where r is a Runnable with #Override public void run() which just calls invalidate();. h is just a handler that is initialized in the class. So each time the game is drawn invalidate will be called after the FRAME_RATE elapse time of 10ms, which tells the game to redraw. All the move functions for the game are also called from the draw method. The OnTouch is happening all along side this process so why would it be smoother if on OnTouch is being called rather than checking if its true, it all seems counter intuitive but I'm sure there is a logical reason. Lastly the lag time was measurable using the system clock and was time dependent based on where exactly it was called. It just showed an increase in passing time between the games move function when a touch event was not occurring.
Sorry for the long response without much actual code context, I hope it is descriptive enough. The code its self is rather long so I didn't think it would help to post that, but if needed I can do better at re posting some pseudo code with the visualized hierarchy. Thank you for any help and suggestions for this problem. It would be great to be able to understand why it is happening.
Here is the code, I was trying to fit it into a comment didn't realize you had to post it by editing the original post.
public class Main extends Activity {
Game game;
private Menus menus; // contains menu info
int menu; //keep track of what menu you are at
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
game = new Game(this);
setContentView(game);
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
..some code here to process photo files
}
//get the orientation of loaded pictures
public static int getOrientation(Context context, Uri photoUri) {
/* it's on the external media. */
Cursor cursor = context.getContentResolver().query(photoUri,
new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);
if (cursor.getCount() != 1) {
return -1;
}
cursor.moveToFirst();
return cursor.getInt(0);
}
public void Keyboard()
{
use keyboard listener and perform some actions if...
}
public class Game extends ImageView{
private Game game;
//Touch Events
boolean pressed;
private Context mContext;
private Handler h;
private final int FRAME_RATE = 10;
boolean initDimension = false;
//constant for defining the time duration between the click that can be considered as double-tap
static final int MAX_DURATION = 500;
Bitmap background;
int HEIGHT;
int WIDTH;
public Game(Context context) {
super(context);
mContext = context;
h = new Handler();
//ModeInit(); // load up puck game mode
menu = 0; //after tap from ontouch changes to menu = 1 for game
}
private Runnable r = new Runnable() {
#Override
public void run() {
invalidate();
}
};
#Override
public boolean onTouchEvent(MotionEvent e)
{
Switch Statement...
CHECK MOTION EVENT TYPE
CHECK menu state.. perform action
}
public void ModeInit()
{
game = new Game( mContext, WIDTH, HEIGHT );
}
protected void onDraw(Canvas c)
{
if(!initDimension)
{
WIDTH = this.getWidth();
HEIGHT = this.getHeight();
initDimension = true;
}
else
{
//logo screen
if(menu == 0)
{
menu = menus.DisplayIntro(c);
}
//game mode
else if(menu == 1)
{
game.paint_game(c);
long run_test = System.nanoTime()/1000;
game.Move();
Log.d("run time: ",": "+(System.nanoTime()/1000-run_test)); //Ontouch LAG TEST
}
... other menu items here
}
h.postDelayed(r, FRAME_RATE);
}
}
}
There are multiple similar questions like mine, but these questions didn't help me.
I'm making a game. The game thread, SurfaceView and Activity is already finished and works so far. The problem is that the canvas is not redrawn. At startup, it draws the icon on the background, but at every tick, the icon doesn't move (It should move once a second). I want to mention, that I never needed to call postInvalidate. I have a working example where I never called it, but it doesn't work in my current example (I don't want to go into it deeper, since I actually don't need to call it). I copied the current code from my working example, the concept and the way of implementation is exactly the same, but my current code doesn't refresh the canvas. When I log the drawing positions in onDraw method, I see that it's coordinates are updated every second as expected, so I can be sure it's a canvas drawing problem. I have searched for hours but I didn't find what's different to my working example (except that I'm using another Android version and I don't extend thread but implement Runnable, because it's a bad style to extend thread. Nevertheless, I also extended thread to see if there is any difference, but it doesn't help). I already tried to clean the canvas by using canvas.drawColor(Color.BLACK), but that didn't help either. I already tried to use background colors instead of a background image which changes randomly every tick, but it didn't change but stays always the same.
I figured out that the canvas at the very first call has a density of (for example) 240. After the second tick, the canvas density is always 0. I know that the density will not help me here, but maybe it's an important information for someone.
Here are the important classes....
game layout, R.layout.game
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.mydomain.mygame.base.game.GameSurface
android:id="#+id/gameSurface"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:background="#drawable/background_game" >
</com.mydomain.mygame.base.game.GameSurface>
<!-- ...more definitions -->
</LinearLayout>
GameActivity (contains layout)
public class GameActivity extends Activity
{
#SuppressWarnings("unused")
private GameSurface gameSurface;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
gameSurface = (GameSurface)findViewById(R.id.gameSurface);
//TODO on button click -> execute methods
}
}
GameSurface (log in onDraw shows updated coordinates every tick)
public class GameSurface extends SurfaceView implements SurfaceHolder.Callback
{
private GameThread thread;
protected final static int TICK_FREQUENCY = 100;// ms, stays always the same. It's a technical constant which doesn't change
private static final String TAG = GameSurface.class.getSimpleName();
public GameSurface(Context context, AttributeSet attrs)
{
super(context, attrs);
ShapeManager.INSTANCE.init(context);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
setFocusable(true); // make sure we get key events
thread = new GameThread(holder, this);
}
public void updateStatus()
{
GameProcessor.INSTANCE.updateShapes();
}
#Override
protected void onDraw(Canvas canvas)
{
for (Shape shape : GameProcessor.INSTANCE.getShapes())
{
Log.i(TAG, "getX()=" + shape.getX() + ", getY()=" + shape.getY());
canvas.drawBitmap(shape.getBitmap(), shape.getX(), shape.getY(), null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
//will never invoked since we only operate in landscape
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
// start the thread here so we don't busy-wait in run
thread.setRunning(true);
new Thread(thread).start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
Log.i(TAG, "executing surfaceDestroyed()...");
thread.setRunning(false);
}
}
GameThread
public class GameThread implements Runnable
{
private SurfaceHolder surfaceHolder;
private boolean running = false;
private GameSurface gameSurface;
private long lastTick;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameSurface)
{
this.surfaceHolder = surfaceHolder;
this.gameSurface = gameSurface;
lastTick = System.currentTimeMillis();
}
#Override
public void run()
{
Canvas canvas;
while (running)
{
canvas = null;
if (System.currentTimeMillis() > lastTick + GameSurface.TICK_FREQUENCY)
{
long timeDifference = System.currentTimeMillis() - (lastTick + GameSurface.TICK_FREQUENCY);
try
{
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
gameSurface.updateStatus();
gameSurface.draw(canvas);
}
}
finally
{
if (canvas != null)
{
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
lastTick = System.currentTimeMillis() - timeDifference;
}
}
}
public void setRunning(boolean running)
{
this.running = running;
}
}
Any ideas why this code doesn't update my canvas? I can't explain it. I do not post ShapeManager and GameProcessor since they don't have anything to do with the problem (and they only load and control the current states and speed of the game).
[UPDATE]
I figured out that onDraw() is invoked before the game thread has started. That means that canvas is passed to this method before thread is using it. The interesting thing is that, after the thread has started, it always uses the same canvas, but it's not the canvas reference which is passed the very first time. Although canvas = surfaceHolder.lockCanvas(null); is assigned every tick, it's always the same reference, but it's not the original reference.
In a working example of mine, the reference is always the same, since I create the bitmaps at constructor initialization time. I can't do that in my current implementation, since I have to do calculations with values I get from onMeasure() which is invoked much later than the constructor.
I tried to somehow pass the original canvas to the thread, but the reference still changes. Meanwhile I think this is the problem, but I don't know how to solve it yet.
As often happens, I found the solution on my own.
Obviously it's really a problem that it draws to different canvas instances. I'm still not sure why this happens. Didn't have the problem before. Nevertheless, I can avoid drawing to canvas by setting setWillNotDraw(true); in my GameSurface constructor and I must not invoke gameSurface.draw(canvas) in my thread, but gameSurface.postInvalidate() instead.
I've got 30+ single bitmaps (320x240 pixels) that I would like to display one after another in full screen on Android devices resulting in an animation. Currently I implemented the animation using an ImageView and a Timer that sets the next frame and then sends a message that will apply the next frame. The resulting frame rate is very low: < 2 fps.
The timer:
animationTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Drawable frame = getNextFrame();
if (frame != null) {
Message message = animationFrameHandler.obtainMessage(1, frame);
animationFrameHandler.sendMessage(message);
}
}
}, 0, (int) (1000.0d / fps));
The handler:
final Handler animationFrameHandler = new Handler() {
#Override
public void handleMessage(Message message) {
setImageDrawable((Drawable) message.obj);
}
};
Since I want to achieve frame rates up to 30 fps I have to make use of another mechanism and heard of Canvas.drawBitmapMesh() and OpenGL.
If possible I would like to avoid using OpenGL.
Thank you very sharing your experiences!
My now working approach is the following:
Before starting the animation, load every frame into a List<Bitmap>. Important: Call System.gc() if you're getting OutOfMemoryErrors – that really helps loading more bitmaps into the memory. Then have a thread running that posts the next frame to a View instance that then update it's canvas.
Loading the frames and starting the animation
// Loading the frames before starting the animation
List<Bitmap> frames = new ArrayList<Bitmap>();
for (int i = 0; i < 30; i++) {
// Load next frame (e. g. from drawable or assets folder)
frames.add(...);
// Do garbage collection every 3rd frame; really helps loading all frames into memory
if (i %% 3 == 0) {
System.gc();
}
}
// Start animation
frameIndex = 0;
animationThread.start();
Thread that applies the next frame
private final class AnimationThread extends Thread {
#Override
public void run() {
while (!isInterrupted()) {
// Post next frame to be displayed
animationView.postFrame(frames.get(frameIndex));
// Apply next frame (restart if last frame has reached)
frameIndex++;
if (frameIndex >= frames.size()) {
frameIndex = 0;
}
try {
sleep(33); // delay between frames in msec (33 msec mean 30 fps)
} catch (InterruptedException e) {
break;
}
}
}
}
The animation view
class AnimationView extends View {
Bitmap frame = null;
public void postFrame(Bitmap frame) {
Message message = frameHandler.obtainMessage(0, frame);
frameHandler.sendMessage(message);
}
protected final Handler frameHandler = new Handler() {
#Override
public void handleMessage(Message message) {
if (message.obj != null) {
frame = (Bitmap) message.obj;
} else {
frame = null;
}
invalidate();
}
}
#Override
protected void onDraw(Canvas canvas) {
if (frame == null) return;
canvas.drawARGB(0, 0, 0, 0);
canvas.drawBitmap(frame, null, null, null);
}
}
You should look at the FrameAnimation class; http://developer.android.com/guide/topics/graphics/2d-graphics.html#frame-animation to do frame animation with Androids animation.
Though that might still be too slow.
The other alternative if you don't want to use OpenGL ES is to draw to the Canvas as you've mentioned. But just use .drawBitmap, not the drawBitmapMesh. Create a SurfaceView, which has a thread, that thread should draw on your Canvas at whatever interval you want.
It's pretty straightforward, just read the Android docs, the information is all there.
I'll let someone else go into the best way of doing this but one thing that immediately jumps to mind from your post that isn't helping is using TimerTask is a terrible way to do this and is not meant for animation.
Probably won't help with performance, but if those bitmaps are resources you might want to consider using an AnimationDrawable. If not, try to extend Drawable and implement the Animatable interface. Views already have built-in support for animating drawables, no need to use a handler for that.
One way to improve performance might be to match the bit-depth of the drawables to those of your current window. Romain Guy did a keynote on this and animations in general once: http://www.youtube.com/watch?v=duefsFTJXzc