I'm new to Android and threading.
What app does
I have a simple app that just shows a bitmap bouncing around within the screen's boundaries (i.e. if bitmap hits the right edge of the screen, it will reverse direction).
Issue
App freezes when I minimize it and also sometimes takes a long time to load and when it loads I see a black screen.
QUESTION
Please try to run the code it is just one file shown below, I just use SurfaceView as layout. There is nothing advanced in term of what app does.
I'm confused as to why we need to use SurfaceHolder.Callback anonymous class (used in a method in the nested SurfaceView class) if there are life cycle methods I write called a pause, resume and stop in nested SurfaceView class that is called by the Main Activity's onPause, onResume, onStop methods. The entire code is shown followed by the structure of app to see an overview that I made because I don't see the point of SurfaceHolder.Callback methods so I mean I don't know why we need to use surfaceCreated, surfaceChanged, surfaceDestroyed and I don't know what to put in surfaceChanged. I also have an onSaveInstanceSate and onRestoreInstance state methods that just save the bitmap's x and y coordinates.
public class MainActivity extends Activity {
GameView gameView;
Handler myHandler;
int xPos = 0;
int yPos = 0;
int deltaX = 3;
int deltaY = 3;
int iconWidth;
int iconHeight;
static final String STATE_POS_X = "playerPosX";
static final String STATE_POS_Y = "playerPosY";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myHandler = new Handler ();
gameView = new GameView(this);
setContentView(gameView);
}
class GameView extends SurfaceView {
private SurfaceHolder surfaceHolder;
private Bitmap bmpIcon;
private MyThread myThread;
volatile boolean playingGame = true;
public GameView(Context context) {
super(context);
init();
}
public GameView(Context context,
AttributeSet attrs) {
super(context, attrs);
init();
}
public GameView(Context context,
AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
myThread = new MyThread(this);
surfaceHolder = getHolder();
bmpIcon = BitmapFactory.decodeResource(getResources(),
R.drawable.rubberBall);
iconWidth = bmpIcon.getWidth();
iconHeight = bmpIcon.getHeight();
surfaceHolder.addCallback( new SurfaceHolder.Callback(){
#Override
public void surfaceCreated(SurfaceHolder holder) {
myThread.setRunning(true);
myThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// What do I enter here???
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
myThread.setRunning(false);
while (retry) {
try {
myThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}});
}
protected void drawSomething(Canvas canvas) {
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmpIcon,
getWidth()/2, getHeight()/2, null);
xPos += deltaX;
if(deltaX > 0){
if(xPos >= getWidth() - iconWidth){
deltaX *= -1;
}
}else{
if(xPos <= 0){
deltaX *= -1;
}
}
yPos += deltaY;
if(deltaY > 0){
if(yPos >= getHeight() - iconHeight){
deltaY *= -1;
}
}else{
if(yPos <= 0){
deltaY *= -1;
}
}
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(bmpIcon,
xPos, yPos, null);
}
public void pause() {
playingGame = false;
try {
myThread.join();
} catch (InterruptedException e) {
}
}
public void resume() {
playingGame = true;
myThread = new MyThread(this);
myThread.start();
}
}//END INNER CLASS: GameView
class MyThread extends Thread {
GameView myView;
private boolean running = false;
public MyThread(GameView view) {
myView = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while (running){
Canvas canvas = myView.getHolder().lockCanvas();
if(canvas != null) {
synchronized (myView.getHolder()) {
myView.drawSomething(canvas);
}
myView.getHolder().unlockCanvasAndPost(canvas);
}
try {
this.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}//END WHILE
}
}//END INNER CLASS: MyThread
/**
* BELOW are Main Activity Life Cycle Call Back methods!
*
*/
#Override
protected void onStop() {
super.onStop();
Log.d("onStop", "Main Activity's onStop called");
while (true) {
gameView.pause();
break;
}
this.finish();
}
#Override
protected void onResume() {
super.onResume();
Log.d("onResume", "Main Activity's onResume called");
gameView.resume();
}
#Override
protected void onPause() {
super.onPause();
Log.d("onPause", "Main Activity's onPause called");
gameView.pause();
}
//save and restore state
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
// Save the user's current game state
savedInstanceState.putInt(STATE_POS_X, xPos);
savedInstanceState.putInt(STATE_POS_Y, yPos);
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
xPos = savedInstanceState.getInt(STATE_POS_X);
yPos = savedInstanceState.getInt(STATE_POS_Y);
}
}//END CLASS: MainActivity
STRUCTURE of MainActivity housing the nested SurfaceView class
MainActivity life cycle methods
--------------------------------
onCreate:
------------
-links up the layout which is the SurfaceView instance
-initialize variables
onPause:
---------
call the SurfaceView's custom pause method here in addition to calling the
super's onPause
onStop:
------
call the SurfaceView's custom stop method here in addition to calling the
super's onStop
onResume:
---------
call the SurfaceView's custom resume method here in addition to calling the
super's onResume
nested Thread class
------------------------
run method to run SurfaceView
nested class SurfaceView's life cycle methods
-------------------------------------------------------
pause method which pauses the worker thread
---------------------------------------------
stop the worker thread:
stop method which stops the worker thread:
-----------------------------------------
stop the worker thread
resume method which creates a new worker thread:
---------------------------------------------------
make a new worker thread
SurfaceHolder.Callback lifespan methods
***Why do we need to write the methods below if I have pause,
resume stop already that will be called by the Main Activity's
onXXX, respectively????
==========================================
surfaceCreated:
---------------
make a new worker thread
surfaceChanged:
---------------
What do I enter here??
surfaceDestroyed:
-----------------
destroy current worker thread
END of nested SurfaceView class
override onSaveInstanceState method:
------------------------------------
save the bitmap's position (x and y coordinates)
override onRestoreInstanceState method:
-----------------------------------------
restore the bitmap's position (x and y coordinates)
END of MainActivity
Check the very well explanation because SurfaceView is more fine tuned control of graphics unlike drawing with a Custom View or using predefined graphical inputs or GUI elems.
http://blog.infrared5.com/2011/07/android-graphics-and-animation-part-ii-animation/
Related
Given the simplified custom view below, what exactly is/isn't running on main thread?
// MainActivity
protected void onCreate(Bundle bundle) {
// ...
CustomView customView = new CustomView(this);
setContentView(customView);
customView.setOnTouchListener((v, event) -> {
customView.setPoint(event.getX(), event.getY());
});
}
public class CustomView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
protected Thread thread;
private boolean running;
private int x;
private int y;
public CustomView(Context context) {
super(context);
thread = new Thread(this);
}
public void run() {
// Get SurfaceHolder -> Canvas
// clear canvas
// draw circle at point <x, y>
// Do some IO?
longRunningMethod();
}
public void surfaceCreated(SurfaceHolder holder) {
running = true;
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
running = false;
}
public void setPoint(int x, int y) {
this.x = x;
this.y = y;
run();
}
private void longRunningMethod(){
// ...
}
}
Is all of CustomView running on a separate thread?
The only thing that is on a separate thread here is this snippet:
public void run() {
// Get SurfaceHolder -> Canvas
// clear canvas
// draw circle at point <x, y>
// Do some IO?
longRunningMethod();
}
Everything else is on your main thread. So anything called from inside run is in your new thread. so surface created, destroyed etc.. are main thread so your "running" variable should probably be lock protected or volatile to make sure you don't create a race condition of getting set out of order at wrong time from separate thread.
Also keep in mind after longRunningMethod() completes you are no longer running that thread unless you put a loop in there to keep it alive.
my app shows a bunch of bitmaps that animate one by one as it's stored in a Bitmap array and initially when app starts up curPosition is 0 to start animating obj at index 0. The handleMessage will animate each obj with a 1000ms delay between each obj when app starts up first time and so when app starts up first time handleMessage animates each obj normally.
The issue is once I double tap the restart button bitmap. I used the GestureDetector class and I have a Restart button constructed via a bitmap that when I listen for onDoubleTap and what should happen is the handleMessage will go thru each bitmap restarting animating them.
For some reason, when I double tap the Restart button bitmap, the handleMessage is running and i know this since I put in a logcat message and for that logcat message in update method I see it running thru each curPosition so I mean this line: Log.i ("cur pos. so far:", "" + curPosition );
but I don't visually see the animation running thru update method unless I interrupt app (i.e. close app OR minimize then resume app) and double tap the Restart button bitmap and the double tapping Restart button bitmap WORKS ONLY ONCE so I need to interrupt app to get it to work again.
public class MainActivity extends Activity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {
private MySurfaceView mysurfaceView;
private Bitmap restart_buttonBitmap;
private GestureDetector mGestureDetector;
private MiniProj2Button restart;
private MiniProj2Button newLevel;
private final int SIZE = 5;
private final int DELAY = 1000;
private Bitmap[] myObjs = new Bitmap[ SIZE ];
//Screen size info
private int screenWidth;
private int screenHeight;
//The size in pixels of a place on the game board
private int blockSize;
private int numBlocksWide;
private int numBlocksHigh;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
// Create a GestureDetector
mGestureDetector = new GestureDetector(this, this);
// Attach listeners that'll be called for double-tap and related gestures
mGestureDetector.setOnDoubleTapListener( this );
}//END METHOD: onCreate
#Override
protected void onStop() {
super.onStop();
Log.d("onStop", "Main Activity's onStop called");
gameView.surfaceDestroyed( mySurfaceView.getHolder() );
this.finish();
}
#Override
protected void onResume() {
super.onResume();
Log.d("onResume", "Main Activity's onResume called");
mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
mySurfaceView.surfaceCreated( mySurfaceView.getHolder() );
}
#Override
protected void onPause() {
super.onPause();
Log.i("onPause", "Main Activity's onPause called");
mySurfaceView.surfaceDestroyed( mySurfaceView.getHolder() );
}
#Override
public boolean onDoubleTap(MotionEvent e) {
//reset animation if Restart button pressed!
if ( restart.isButtonTouched(e.getX(), e.getY(), blockSize,blockSize) ) {
curPosition = 0;
}
return true;
}
public void configureDisplay() {
//find out the width and height of the screen
Display display =
getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenWidth = size.x;
screenHeight = size.y;
//Determine the size of each block/place on the board
// blockSize = screenWidth/10;
blockSize = screenWidth/10;
//Determine how many blocks will fit into the
//height and width
// numBlocksWide = 10;
numBlocksWide = 10;
numBlocksHigh = ( ( screenHeight ) ) / blockSize;
}//END METHOD: configureDisplay
#Override
public boolean onTouchEvent(MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return super.onTouchEvent(e);
}//END onTouchEvent
class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Handler.Callback {
Canvas canvas = new Canvas();
SurfaceHolder surfaceHolder;//this lets us access the canvas but still need to use Canvas class
Handler myHandler;
Paint paint = new Paint ();
public MySurfaceView(Context context) {
super(context);
paint.setColor(Color.argb(255, 0, 0, 0));
path = new Path ();
myHandler = new Handler(this);
getHolder().addCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
this.surfaceHolder = holder;
myHandler.sendEmptyMessage(0);
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// What do I enter here???
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("Me", "surfaceDestroyed: bye!" );
myHandler.removeCallbacksAndMessages(null);
}
protected void drawIt ( ) {
if ( surfaceHolder.getSurface().isValid() ) {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);//the background
//for each obj, draw it
surfaceHolder.unlockCanvasAndPost(canvas);
}//END IF BLOCK: For valid surfaceHolder
}//END METHOD: drawIt
public void update() {
Log.i ("cur pos. so far:", "" + curPosition );
if ( curPosition < SIZE ) {
myObjs [ curPosition ].animateMe ( );
curPosition++;
}
}//END METHOD: update
#Override
public boolean handleMessage(Message msg) {
if (curPosition < SIZE ) {
Log.i("Still objs 2 animate","Yes");
Log.i ("cur pos. so far:", "" + curPosition );
gameView.update();
gameView.drawIt();
myHandler.sendEmptyMessageDelayed(msg.what, DELAY);
}
else {
myHandler.sendEmptyMessageDelayed(msg.what, 10);
gameView.drawIt();
}
return true;
}//END METHOD: handleMessage
}//END INNER CLASS: MySurfaceView
}//END CLASS: MainActivity
i am trying to run a simple button animation when the game is over
i have the following classes:
GameView
GameThread
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
boolean gameover;GameThread gamethread;
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
gameover=true;
getHolder().addCallback(this); // adding the callback (this) to the surface holder to intercept events
setFocusable(true);// make the GamePanel focusable so it can handle events
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.d("TAG", "surface changed");
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gamethread=new GameThread(getHolder(),this);
gamethread.setrunning(true);
gamethread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry=true;
Log.d("TAG", "Surface destroyed entered");
while(retry){
try {
gamethread.setrunning(false);
gamethread.join();
gamethread=null;
Log.d("tag","thread is destroyed" );
if(gamethread==null){
Log.d("tag","thread is destroyed and null" );
}
retry=false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}//end while
}//end method
public void update(){
if(gameover){
Button gameover_button = (Button) findViewById(R.id.gameover);
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.gameoveranimation);
gameover_button.startAnimation(animation);
}
}//end update`
public class GameThread extends Thread {
// desired fps
private boolean running;
private SurfaceHolder surfaceholder;
private GameView gameview;
public GameThread(SurfaceHolder surfaceHolder, GameView gameview){
this.gameview=gameview;
this.surfaceholder=surfaceHolder; //we need the surfaceholder since we need to lock surface before drawing
}
public void setrunning(boolean running){
this.running=running;
}
#Override
public void run(){
super.run();
Canvas canvas;
while(running){
canvas=null;
canvas = this.surfaceholder.lockCanvas();
synchronized (surfaceholder) {
if(canvas!=null){
canvas.drawColor(Color.BLACK);
this.gameview.Draw1(canvas);
this.gameview.update();
}//end if
}
}
}finally{//in case of an exception
if(canvas!=null){
surfaceholder.unlockCanvasAndPost(canvas);
}
}//end finally
}//end loop
}//end run
}
<br>
everything works fine except the animation in the update method in the gameview class
the animation runs fine in the oncreate method
i know that my problem is related to thread but i don't know much about them
thx in advance
`
If your issue is that you need to call update on the UI thread, then you can use this:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
this.gameview.update();
}
});
Also, it looks like you are looking for your game-over button as a child of your surface view. If that is not correct, you can find the view relative to the parent activity.
Activity activity = (Activity) getContext();
Button gameover_button = (Button) activity.findViewById(R.id.gameover);
I just made an Activity which uses at "setContentView" a view from a class which extends SurfaceView. The problem is:
It works fine, but when I exit it (BACK key) it crashes. Code:
package ro.etrandafir.mate.appCreator;
import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.View;
import android.view.SurfaceView;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.graphics.Color;
import android.graphics.Paint;
public class Sample2 extends Activity implements View.OnTouchListener {
float x = 0, y = 0;
SampleTwoView theView;
public boolean onTouch(View v, MotionEvent event) {
// TODO: Implement this method
x = event.getX();
y = event.getY();
return true;
}
#Override
protected void onPause() {
super.onPause();
finish();
}
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
theView = new SampleTwoView(this);
theView.setOnTouchListener(this);
setContentView(theView);
}
public class SampleTwoView extends SurfaceView implements Runnable {
Paint p = new Paint();
public SampleTwoView(Context context) {
super(context);
p.setColor(Color.RED);
Thread theThread = new Thread(this);
theThread.start();
}
public void run() {
while (true) {
if (!getHolder().getSurface().isValid()) continue;
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
getHolder().unlockCanvasAndPost(canvas);
}
}
}
}
What can I do? Should I add onDestroy or what?
Thanks in advance,
Matei
The issue you are getting is related to this code:
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
When your activity ends, your thread is still running, but your custom SurfaceView is no longer available, so you will get a null ptr exception. Your existing code can easily be patched by adding a boolean that gets set to false as soon as the onPause fn gets called:
public void run() {
while (booleanThatGetsSetToFalseWhenActivityPauses) {
if (!getHolder().getSurface().isValid()) continue;
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
getHolder().unlockCanvasAndPost(canvas);
}
}
However, I would suggest altering the structure of your application as a whole. This may just be for practice, but I think a more efficient and bug free way of accomplishing your goal would be to simply use a standard SurfaceView and to completely decouple your drawing logic from any custom view.
My redesigned activity is below, but it utilizes a Ball class that is used to maintain the ball's logic, which, in your current code is separately coupled with both the actvity (the coordinates) and the view (the Paint). In this new ball class, a ball has a location (specified by a PointF), a Paint, and a diameter. It also has methods to get most of these variables in addition to setting some.
public class Ball {
private Paint mPaint;
private PointF mCoordinates;
private int mDiameter;
public Ball (int color, int diameter) {
mPaint = new Paint();
mPaint.setColor(color);
mCoordinates = new PointF();
mCoordinates.x = 0;
mCoordinates.y = 0;
mDiameter = diameter;
}
public void setCoordinates (float x, float y) {
mCoordinates.x = x;
mCoordinates.y = y;
}
public PointF getCoordinates() {
return mCoordinates;
}
public Paint getPaint() {
return mPaint;
}
public int getDiameter() {
return mDiameter;
}
/* You did not want to draw the uninitialized ball, so this method checks that */
public boolean hasNonZeroLocation () {
return (mCoordinates.x != 0 && mCoordinates.y != 0);
}
}
I use the Ball class in the activity as shown below. Notice that the redrawing to the canvas now only occurs when a user touches the canvas as opposed to an infinite while loop. This is due to the utilization of the Handler class which posts actions to run to the UI thread. Additionally, now we do not need a custom view, and our ball's logic has been decoupled from the activity and the view.
public class RedBallActivity extends Activity {
Handler mDrawingHandler;
SurfaceView mDrawingSurfaceView;
Ball mBall;
private final Runnable drawRedBallOnBlueSurface = new Runnable() {
#Override
public void run() {
if (!mDrawingSurfaceView.getHolder().getSurface().isValid()) return;
Canvas canvas = mDrawingSurfaceView.getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if (mBall.hasNonZeroLocation())
canvas.drawCircle(mBall.getCoordinates().x, mBall.getCoordinates().y, mBall.getDiameter(), mBall.getPaint());
mDrawingSurfaceView.getHolder().unlockCanvasAndPost(canvas);
}
};
private final OnTouchListener mCanvasTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mBall.setCoordinates(event.getX(), event.getY());
mDrawingHandler.post(drawRedBallOnBlueSurface);
return true;
}
};
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
mDrawingSurfaceView = new SurfaceView(this);
mDrawingSurfaceView.setOnTouchListener(mCanvasTouchListener);
setContentView(mDrawingSurfaceView);
mBall = new Ball(Color.RED, 40);
mDrawingHandler = new Handler();
}
}
Now, if you actually run this code you will notice that initially the screen is not drawn with a blue background. You might be tempted to simply call mDrawingHandler.post(drawRedBallOnBlueSurface); at the end of the onCreate method, but it is not guaranteed that the SurfaceView will be ready to be drawn upon (see the documentation on this lockCanvas method). If you want the surface to initially be blue, you need to implement a [SurfaceHolder.Callback][2], which needs to be connected to the SurfaceView's SurfaceHolder, and on the surfaceCreated method being called, we know the surface is ready, so we can then call mDrawingHandler.post(drawRedBallOnBlueSurface);
Now, with this added, I change the Activity to implement [SurfaceHolder.Callback][2] as follows:
public class FriendManagerActivity extends Activity implements SurfaceHolder.Callback {
and add this line to the constructor:
mDrawingSurfaceView.getHolder().addCallback(this);
and implement the interface:
#Override
public void surfaceCreated(SurfaceHolder holder) {
mDrawingHandler.post(drawRedBallOnBlueSurface);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
Feel free to ask any questions on my little redesign! While your problem could be easily patched, I felt like the way you were coupling logic with Views was a little bit flawed, and thought a little more info on SurfaceView coding would be helpful.
As someone mentioned it in the above, when your activity ends, your thread is still running, but your custom SurfaceView is no longer available, so you will get a Null Point Exception. Your existing code can easily be patched by adding a boolean that gets set to false as soon as the onPause fn gets called:I had the same problem. To solve it I added the following onPause() to your SampleTwoView class:
// pause method will destroy the Thread
public void pause() {
isRunning = false;
while (true) {
try {
myThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
myThread = null;
}
Then call this onPause() method in your onPause() method of your Sample2 class as follows:
#Override
protected void onPause() {
super.onPause();
SampleTwoView.onPause();
finish();
}
So everytime the onPause() method of your main Activity class is called the Thread will be destroyed.
I hope this will help you.
Cheers!
I am experimenting with drawing on a canvas using a thread to create a simple game engine but I'm having some weird issues I cannot explain.
The purpose of this "game" is to draw a circle every second on the canvas.
This works, but not the way I want it to work, it seems the app is switching between two canvasses and adding a circle to each canvas so you get a switch between two canvasses every second with the same number of circles but in a different place on the canvas.
I don't know what I'm doing wrong, but I'm not that familiar with Treadding, has it something to do with how many cores my android device has or something like that?
My code is shown below, so I just use a launchthread which uses a layoutfile that links to the animationthread which starts a thread and draws a circle on the canvas every second.
(You can ignore the touchevent, it isn't uses yet).
The project exists out of a main launchthread:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
which uses this layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.androidtesting.AnimationView
android:id="#+id/aview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</FrameLayout>
And my Surfaceview class with an inner Thread class:
class AnimationView extends SurfaceView implements SurfaceHolder.Callback {
private boolean touched = false;
private float touched_x, touched_y = 0;
private Paint paint;
private Canvas c;
private Random random;
private AnimationThread thread;
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
thread = new AnimationThread(holder);
}
class AnimationThread extends Thread {
private boolean mRun;
private SurfaceHolder mSurfaceHolder;
public AnimationThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
paint = new Paint();
paint.setARGB(255,255,255,255);
paint.setTextSize(32);
}
#Override
public void run() {
while (mRun) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
doDraw(c);
sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
private void doDraw(Canvas canvas) {
//clear the canvas
//canvas.drawColor(Color.BLACK);
random = new Random();
int w = canvas.getWidth();
int h = canvas.getHeight();
int x = random.nextInt(w-50);
int y = random.nextInt(h-50);
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
int size = 20;
canvas.drawCircle(x,y,size,paint);
canvas.restore();
}
public void setRunning(boolean b) {
mRun = b;
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public boolean onTouchEvent(MotionEvent event) {
touched_x = event.getX();
touched_y = event.getY();
int action = event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
touched = true;
break;
case MotionEvent.ACTION_MOVE:
touched = true;
break;
default:
touched = false;
break;
}
return true;
}
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
it seems the app is switching between two canvasses
Yes, this is how it works. It is called double buffering and you need to redraw all the frame each time:
The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written.
So you need this line canvas.drawColor(Color.BLACK) to be uncommented in your code.
And you shouldn't call Thread.sleep(1000) while canvas is locked, it will cause starvation issue.
It sounds like you have this working, but I did just notice a small error that I should point out.
You called canvas.restore() without calling canvas.save() beforehand.
From the Android developer reference for Canvas: "It is an error to call restore() more times than save() was called."
I don't see any reason for you to call canvas.save() in your case, therefore you should remove the call to canvas.restore().