I am looking for an easy way to animate custom views in Android. I am trying to avoid using the animator object but want to work with raw threads. What I have done is created a custom view by creating a class that extends android.view.View. I then override the onDraw method and use the canvas to draw a rect. What I would like is the rect to shrink, so I keep a variable that represents the x value of the right hand side of the rectangle. I would then like the the rectangle's right edge shrink in over time. What I would have liked to do is create a new thread, start it and have it change the value of the rectangle. That all works except the view doesn't get updated until you call View.invalidate. The problem is that I can't call that from the thread that I spawned because it is not the UI thread. I read solutions on using Handlers... but I am still unsure if that is the correct solution and then how to use them.
package com.example.practicum;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
public class TimerControl extends View implements Runnable, Handler.Callback
{
private Paint paint;
private Rect rect;
private Thread t;
private Handler h;
public TimerControl(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
paint = new Paint();
paint.setColor(Color.BLUE);
rect = new Rect(0,0,60,60);
t = new Thread(this);
t.start();
h = new Handler(this);
//h.post(this);
}
#Override
public void onDraw(Canvas canvas)
{
canvas.drawRect(rect, paint);
}
#Override
public void run()
{
rect.right = rect.right-1;
while(true)
{
rect.right = rect.right-1;
this.invalidate();
try
{
Thread.sleep(5000);
h.sendEmptyMessage(0);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
#Override
public boolean handleMessage(Message msg)
{
return false;
}
}
you can define a global Handler (in the UI thread):
Handler mHandler = new Handler();
then, from your thread, call:
mHandler.post(new Runnable() {
public void run() {
// call some method on the UI thread
}
});
Related
I am trying to set a text which acts as a timer. I'll create timer easily if i extend Activity class. But i am facing lot of problem in this View class. Here i made a code where radius of circle will be reducing and it will be increased for each touch on screen. Simultaneously i need a timer running on the top. Because i need to increase the reducing speed after every 10 seconds.
So here timer plays an important role. I tried using thread , which should update the text for every 1 second . But it is showing this exception IllegalThreadStateException.
Is there any other way to implement timer in this screen ?
Or What mistake i did in my code..
Thanks for you help friends..
package com.kbt.testcircle;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class MyView extends View {
int radius = 100,i=0;
boolean freeTouched = false;
Path freePath;
Handler handler = new Handler();
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void calThread() {
/*
* Thread th=new Thread(){ public void run(){ try { sleep(2000); } catch
* (InterruptedException e) { // TODO Auto-generated catch block
* e.printStackTrace(); } finally{ radius-=1;
* Log.i("kbt","inside thread 800 seconds"); } } }; th.start();
*/
handler.postDelayed(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
radius -= 1;
Log.i("kbt", "inside thread 800 seconds");
invalidate();
}
}, 500);
Log.i("kbt", "Inside thread");
}
Thread th=new Thread()
{
public void run(){
handler.postDelayed(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
i++;
}
},1000);
}
};
#Override
protected void onDraw(Canvas canvas) throws IllegalThreadStateException
{
// TODO Auto-generated method stub
th.start();
Log.i("kbt","starting");
super.onDraw(canvas);// th.start();
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(3);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, paint);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(0);
paint.setTextSize(25);
canvas.drawText("Timer : "+i, getWidth()/4, getHeight()/4, paint);
calThread();
Log.i("kbt", "Inside clled");
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_DOWN:
radius+=5;
invalidate();
/* freeTouched = true;
freePath = new Path();
freePath.moveTo(event.getX(), event.getY()); */
break;
case MotionEvent.ACTION_MOVE:
/* freePath.lineTo(event.getX(), event.getY());
invalidate(); */
break;
}
return true;
}
}
But it is showing this exception IllegalThreadStateException.
It caused by non-runnable or non-threading class. A class that extends View has no background thread on it, so that you get IllegalThreadStateException.
Is there any other way to implement timer in this screen?
Java provided a Timer class to be used for all of class types:
Timer timer = new Timer();
TimerTask task = new TimerTask(){
#Override
public void run() {
// do your thing here
}
};
timer.schedule(task,
1, // delay the task 1ms before executed
10000); // repeating your task every 10000ms, i.e. 10 seconds
If you really one to use a thread, you could only start the thread once. If you want to start thread for many times, use Runnable instead.
I am developing a game for android. Between stages, i want to show a part of a map with a route, and move it from a city to city (stage to stage).
First i want to do it on my phone, this a Samsung Galaxy Y, 240x320 Qvga ldpi.
So i have the map file in jpg format. This picture is 2463x602, this is a world map.
I did it, everything is done except one thing. This "animation" is slow for me.
When i start with this, i thought, it will be so fast, and i will handle the speed with a Thread.sleep(); but the matter is, it is not fast.
How can i make it more faster this?
Here is my code:
package hu.mycompany.myproject;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MapActivity extends Activity {
DrawView drawView;
SoundPool soundPool;
int soundId;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
soundId = soundPool.load(this, R.raw.airplane, 1);
drawView = new DrawView(this);
setContentView(drawView);
}
public void playSound() {
soundPool.play(soundId, 1f, 1f, 1, 0, 1f);
}
#Override
public void onResume() {
super.onResume();
playSound();
drawView.resume();
}
#Override
public void onPause() {
super.onPause();
drawView.pause();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_map, menu);
return true;
}
public class DrawView extends SurfaceView implements Runnable {
Bitmap gameMap = null;
Thread gameLoop = null;
SurfaceHolder surface;
Rect rect;
volatile boolean running;
volatile boolean moved;
public DrawView(Context context) {
super(context);
moved = false;
surface = getHolder();
gameMap = BitmapFactory.decodeResource(getResources(),
R.drawable.map);
}
public void resume() {
running = true;
gameLoop = new Thread(this);
gameLoop.start();
}
public void pause() {
running = false;
while (true) {
try {
gameLoop.join();
} catch (InterruptedException e) {
}
}
}
#Override
public void run() {
Rect canvasSize = new Rect(0, 0, 240, 320);
Paint paint = new Paint();
paint.setAntiAlias(true);
while (running) {
if (!surface.getSurface().isValid()) {
continue;
}
if (!moved) {
int i;
for (i = 80; i <= 830; i++) {
Canvas canvas = surface.lockCanvas();
rect = new Rect(i, 250, (i + 240), 570);
canvas.drawBitmap(gameMap, rect, canvasSize, paint);
surface.unlockCanvasAndPost(canvas);
}
moved = true;
}
}
}
}
}
1-try to reduce picture size by exporting it save for web in photoshop and select png as extension type
2- You should create two class one for handling thread and other for rendering and game logic. The thread will manage it by locking canvas when system is rendering. Take a look at this link
http://android-er.blogspot.com/2010/05/android-surfaceview.html
I'm developing a 2d Game using Canvas/Surfaceview and have a problem with scrolling my background image.
Check out the game - http://youtu.be/4Gi5rRqzZ3M
In the NinJump game, the character Ninja is just jumping in X coordinates and Background image is scrolling at a very high speed, making Ninja look like it is actually running.
I have created the basic setup, created the Ninja, added jump functionality, added background. Now I want to repeat the same background over and over again. How can I accomplish that?
Below are my source files - Main Activity Class
package com.abc.apps;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
public class LadderActivity extends Activity {
private static final String TAG = LadderActivity.class.getSimpleName();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// requesting to turn the title OFF
requestWindowFeature(Window.FEATURE_NO_TITLE);
// making it full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set our MainGamePanel as the View
setContentView(new MainGameBoard(this));
Log.d(TAG, "View added");
}
#Override
protected void onDestroy() {
Log.d(TAG, "Destroying...");
super.onDestroy();
}
#Override
protected void onStop() {
Log.d(TAG, "Stopping...");
super.onStop();
}
}
Game Board extends SurfaceView
package com.abc.apps;
import android.app.Activity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGameBoard extends SurfaceView implements SurfaceHolder.Callback{
private MainGameLoop thread;
private Monkey monkey;
private static final String TAG = MainGameLoop.class.getSimpleName();
int currentX, currentY;
public MainGameBoard(Context context) {
super(context);
// TODO Auto-generated constructor stub
// adding the callback (this) to the surface holder to intercept events
//This line sets the current class (MainGamePanel) as the handler for the events happening on the actual surface
getHolder().addCallback(this);
// create monkey and load bitmap INITIALIZE AT LEFT
monkey = new Monkey(BitmapFactory.decodeResource(getResources(), R.drawable.actor),60, 340);
// create the game loop thread
thread = new MainGameLoop(getHolder(), this);
// make the GamePanel focusable so it can handle events.
setFocusable(true);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
}
catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//For jumping Left
if (event.getX() < (getWidth()/2 - 32)) {
// Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
//Log.d(TAG, "Jump Left");
// Sleep so that the main thread doesn't get flooded with UI events.
try {
Thread.sleep(32);
monkey.setX((getWidth()/2 - 60));
monkey.setY(monkey.getY()-70);
} catch (InterruptedException e) {
// No big deal if this sleep is interrupted.
}
}
//For Jumping Right
if (event.getX() > (getWidth()/2 + 32)) {
//Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
//Log.d(TAG, "Jump Right");
// Sleep so that the main thread doesn't get flooded with UI events.
try {
Thread.sleep(32);
monkey.setX((getWidth()/2 + 60));
monkey.setY(monkey.getY()-70);
} catch (InterruptedException e) {
// No big deal if this sleep is interrupted.
}
}
/* //Middle Portion
if (event.getX() > (getWidth()/2 - 32) && event.getX() < (getWidth()/2 +32)) {
//thread.setRunning(false);
//((Activity)getContext()).finish();
}*/
}
return true;
}
public void render(Canvas canvas) {
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, 0,null);
monkey.draw(canvas);
}
/* #Override
protected void onDraw(Canvas canvas){
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_scene), 0, 0,null);
monkey.draw(canvas);
}*/
}
Main Game Loop
package com.abc.apps;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainGameLoop extends Thread {
private SurfaceHolder surfaceHolder;
private MainGameBoard gameBoard;
private Monkey monkey;
private static final String TAG = MainGameLoop.class.getSimpleName();
// flag to hold game state
private boolean running = true;
public void setRunning(boolean running) {
this.running = running;
}
#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 = surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// update game state
// render state to the screen
// draws the canvas on the panel
gameBoard.render(canvas);
}
}
finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
public MainGameLoop(SurfaceHolder surfaceHolder, MainGameBoard gameBoard) {
super();
this.surfaceHolder = surfaceHolder;
this.gameBoard = gameBoard;
}
}//MainThread
Monkey Class
package com.abc.apps;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
public class Monkey {
private Bitmap bitmap; // the actual bitmap
private int x; // the X coordinate
private int y; // the Y coordinate
private boolean touched; // if monkey is touched
public Monkey(Bitmap bitmap, int x, int y) {
this.bitmap = bitmap;
this.x = x;
this.y = y;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isTouched() {
return touched;
}
public void setTouched(boolean touched) {
this.touched = touched;
}
public void draw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2), y, paint);
}
}
It looks like you are drawing your background in your MainGameBoard class in the render method.
public void render(Canvas canvas) {
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, 0,null);
monkey.draw(canvas);
}
You should just need 2 drawBitmap calls instead of 1 there.
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, y_offset1,null);
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.monkey_sc), 0, y_offset2,null);
I'm making an assumption that each background image has the same height or greater than the screen height; if it is less than the screen height you would need more than 2 instances.
Then you start 1 image at y_offset1 = 0 and the other at y_offset2 = -image_height.
Each draw you would increase y_offset1 and y_offset2 by the same amount. You would then need to do a check for both offsets to see if either has an amount greater than the screen height. If it does then the y_offset that is now "below screen" should be reset to the other y_offset minus the image_height. This will create a scroll image that loops indefinitely.
When using this type of technique it is important to think about your image edges; the image should be designed such that they tile seamlessly, otherwise at the looping point there is a noticeable visual artifact along the edge.
i am trying to find a way that the thread can be shut down and restarted without the program crashing. It is called from a menu and an activity sets the panel as its content view and i wish that when the return arrow is pressed on the android that it returns to the activity and then the thread can be restarted however currently any variation i try causes it to crash at one point or another :(
package SortItOut.sortitout;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Level1Panel extends SurfaceView implements
SurfaceHolder.Callback {
private Level1Thread thread;
static Bitmap background;
static int x = 0;
public Level1Panel(Context context) {
super(context);
getHolder().addCallback(this);
background = BitmapFactory.decodeResource(getResources(), R.drawable.gamebackground);
thread = new Level1Thread(getHolder(), this);
setFocusable(true);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
thread.stop();
}
public void render(Canvas canvas)
{
canvas.drawBitmap(background, x, 0, null);
x = x + 20;
}
}
======Thread=======
package SortItOut.sortitout;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class Level1Thread extends Thread {
private boolean running;
private SurfaceHolder surfaceHolder;
private Level1Panel gamePanel;
public void setRunning(boolean running) {
this.running = running;
}
public Level1Thread(SurfaceHolder surfaceHolder, Level1Panel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
public void run() {
Canvas canvas;
while (running) {
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
this.gamePanel.render(canvas);
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
You should not use thred.stop(); You must allow the thred to stop on its own by stopping the loop within it.
Check the second example of SurfaceView aniamtion: How can I use the animation framework inside the canvas?
Here are some suggestions (without getting any details on the exception of the crash):
You should Implement Runnable instead of Extend Thread.
You should let the thread know that it should exit the loop.
You should interrupt() the thread instead of calling stop() (interrupt also takes the thread out of a blocking state).
You should handle the InterruptedException inside the run method.
You should exit gracefully when you're interrupted (i.e. finish up whatever you're doing and clean up).
I'm sure I'm missing some things, but generally this should keep you out of trouble. Again, without knowing the specific exception you're getting, I would assume that you're calling stop and the thread is not cleaning up properly at whatever state it was when running, thus you get the crashes at one point or another (depending on what's corrupted with the state).
For simpleness of the question, I'm drawing an integer on a SurfaceView which increases by 1 every draw.
The increasing actually happens, as I can see on the System.out.
The text on the screen stays on '0'.
Who can tell me what I'm doing wrong?
SurfaceViewTest.java
package com.niek.surfaceviewtest;
import android.app.Activity;
import android.os.Bundle;
public class SurfaceViewTest extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
StatusView.java
package com.niek.surfaceviewtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class StatusView extends SurfaceView implements SurfaceHolder.Callback {
private int tmp;
private DrawThread drawThread;
public StatusView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
setFocusable(true);
drawThread = new DrawThread(getHolder());
}
#Override
public void onDraw(Canvas c) {
c.drawColor(Color.BLACK);
Paint p = new Paint();
p.setColor(Color.RED);
c.drawText(tmp + "", 10, 10, p);
tmp++;
System.out.println(tmp);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
drawThread.setRunning(true);
drawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
drawThread.setRunning(false);
while (retry) {
try {
drawThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
protected class DrawThread extends Thread {
private SurfaceHolder surfaceHolder;
private boolean isRunning;
public DrawThread(SurfaceHolder surfaceHolder) {
this.surfaceHolder = surfaceHolder;
isRunning = false;
}
public void setRunning(boolean run) {
isRunning = run;
}
public void run() {
Canvas c;
while (isRunning) {
c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF">
<com.niek.surfaceviewtest.StatusView
android:id="#+id/statusview1"
android:layout_width="fill_parent"
android:layout_height="30dip"
android:background="#000000"
/>
</LinearLayout>
My only guess is that painting isn't being done on the surface because the view isn't invalidated. You're supposed to call invalidate() in order to draw, and then let the framework call onDraw(). Maybe that's why tmp is being incremented, but the paint operation only reaches the surface the first time.
It might be worth experimenting: maybe make Canvas c a member of StatusView, and then replace
synchronized (surfaceHolder) {
onDraw(c);
}
with
synchronized (surfaceHolder) {
invalidate();
}
Does that work?
The current solution is not right. You are using the SurfaceView in order to update the content from a separate thread and not using the invalidate() method that will run onDraw() method when the system refreshes the content. The problem is that you have set a background for your StatusView, try deleting that line
android:background="#000000"
apparently, you need to control the whole information displayed in that view.
Looks like you are showing your main.xml with setContentView(R.layout.main); instead of creating the surface and displaying that. unless i am missing code somewhere I don't see that as being the case.