For normal Activity with the help of ImageView we are able to attain Frame Animation ("Through some online tutorial i done that")
img = (ImageView) findViewById(R.id.myimage);
img.setBackgroundResource(R.drawable.loadimage);
AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground();
frameAnimation.start();
but here in Canvas i'm using Bitmap to get an image to the view, their i'm stuck..
Resources res = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.loadimage);
is their any way to do frame animation in canvas..?
You can create an animated custom view using SurfaceView with a Thread to schedule the animation times properly.
The idea is to extend from SurfaceView, get the Canvas from the SurfaceHolder, draw on it, post the draw on the screen, and repeat (done in a background thread). A basic example:
public class CustomView extends SurfaceView implements Runnable {
Thread animationThread = null;
SurfaceHolder holder;
volatile boolean running = false;
public CustomView(Context context) {
super(context);
holder = this.getHolder();
}
// called from Activity's onResume() to start the thread
public void resume() {
running = true;
animationThread = new Thread(this);
animationThread.start();
}
// called from Activity's onPause() to end the thread
public void pause() {
running = false;
while(true) {
try {
animationThread.join();
break;
} catch (InterruptedException e) { }
}
}
// run until pause() is called
#Override
public void run() {
while(running) {
if(!holder.getSurface().isValid())
continue;
Canvas canvas = holder.lockCanvas();
// your drawing logic goes here:
// - calculate the time of the frame
// - draw a different bitmap each time ...
canvas.drawBitmap(...)
holder.unlockCanvasAndPost(canvas);
}
}
}
To handle the size of the view, you would need to override onMeasure() and onSizeChanged().
For a better understanding of how this works:
http://blog.infrared5.com/2011/06/android-graphics-and-animation-part-i/
http://blog.infrared5.com/2011/07/android-graphics-and-animation-part-ii-animation/
http://developer.android.com/reference/android/view/SurfaceView.html
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 this android application.
It use a SurfaceView, from where I get the Surface through the SurfaceHolder.
It also use ExoPlayer to stream videos. However I have instantiated an ImageReader, getting its Surface and passing to the ExoPlayer.
Now, I am in the ImageReader.OnImageAvailableListener#onImageAvailable and I access the latest Image.
I want to manipulate the Image and send the new data to the "SurfaceView" Surface.
How can I "draw" an android.media.Image to an android.view.Surface ?
The question is not clear to me.
The way to get android.media.Image is by the Camera2 API, and there you can provide a surface and the "camera" will draw over it. Please refer to Camera2Video example
Another way to get the Image object is from ImageReader (while decoding video for example). In this case you want to draw the image, but you can not provide a surface to the ImageReader(there is an internal surface that is not displayed). In this case you can draw the Image on a SurfaceView.
Assuming this is the case, you need to convert an Image to a Bitmap objects.
You have discussion about how perform this here
Possible duplicate of: how to draw image on surfaceview android
First get your canvas by using lockCanvas() (see here), second get your image and make it a drawable using:
my_bitmap = Bitmap.createBitmap(
MediaStore.Images.Media.getBitmap(getContentResolver(), uri),
0,0,90, 90);
drawable=new BitmapDrawable(my_bitmap);
After that you can draw the drawable to the locked canvas and use unlockCanvasAndPost (Canvas canvas) to post the updated canvas back to the surfaceview.
here is the answer for your question.
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mySurfaceView mySurfaceView = new mySurfaceView(getApplicationContext());
setContentView(mySurfaceView);
}
}
mySurfaceView.java
public class mySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private TutorialThread _thread;
public mySurfaceView(Context context) {
super(context);
getHolder().addCallback(this);
_thread = new TutorialThread(getHolder(), this);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap _scratch = BitmapFactory.decodeResource(getResources(),
R.drawable.icon);
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(_scratch, 10, 10, null);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
public void surfaceCreated(SurfaceHolder arg0) {
_thread.setRunning(true);
_thread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
class TutorialThread extends Thread {
private SurfaceHolder _surfaceHolder;
private mySurfaceView _panel;
private boolean _run = false;
public TutorialThread(SurfaceHolder surfaceHolder, mySurfaceView panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.onDraw(c);
}
} finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
One activity in my application takes pictures, then calls another activity that opens the pictures for editing. Problem is, I can't get the picture I've taken to show up in my canvas the right way.
Structure is like so:
Layout
<RelativeLayout>
<SurfaceView id="the_surface_view" />
<Other_Stuff />
</RelativeLayout>
Activity
class EditorActivity extends Activity implements SurfaceHolder.Callback {
private Bitmap image;
private Matrix rotationMatrix;
...
public void onCreate(){
...
SurfaceView surface = (SurfaceView) findViewById(R.id.the_surface_view);
SurfaceHolder holder = surface.getHolder();
holder.addCallback(this);
editor.setWillNotDraw(false);
String imageFile = getIntent.getStringExtra("file");
image = BitmapFactory.decodeFile(imageFile);
rotationMatrix = new Matrix();
rotationMatrix.postRotate(90);
// same angle as the camera preview, will be dynamic later
...
}
protected void onDraw(canvas canvas){
canvas.drawRGB(255,0,255); // just to make sure that it works
canvas.drawBitmap(image, rotationMatrix, null);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
synchronized (holder){
onDraw(canvas);
}
} catch (Exception e){
Log.e(TAG, "failed canvas lock.",e);
} finally {
if (canvas != null){
holder.unlockCanvasAndPost(canvas);
}
}
}
...
}
All that happens with this is that the canvas turns magenta.
Weirdly, when I comment out canvas.postRotation(90), the image shows up (albeit with the wrong rotation).
How do I get my image to show up correctly?
I am coding a snake game in Android, and I am blocked by the fact that i can't draw a wall using a loop inside onDraw method, the wall just don't show up.
Is there a way i can draw the wall so that the onDraw will keep it intact and keep on redrawing others things (snake and objects) ?
GameView:
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
protected void onDraw(final Canvas canvas) {
canvas.drawColor(Color.WHITE);
if(drawWall) {
int i = 0;
while(i < getWidth()) {
canvas.drawBitmap(wall.getImage(), i, 0, null);
i += 17;
}
drawWall = false;
}
x += dx;
y += dy;
}
GameLoop:
public class GameLoopThread extends Thread {
private GameView view;
private boolean running = false;
public GameLoopThread(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while (running) {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
}
I don't know what your view hierarchy is, however, why not make your life easier and just have an ImageView behind your SurfaceView with your walls on it? Or have a FrameLayout contain your SurfaceView and have the walls as a background image for the FrameLayout?The collision with the walls is going to be handled in the code and the graphical representation of the walls is really just for the users benefit.
Put the images of the walls in the background and let Android worry about it, in your SurfaceView just concentrate of redrawing the snake and the dots.
That being said, looks like you're drawing in a background thread. You can't actually do that on Android and you should be drawing in the main thread.
I have implemented a gameloop to work on SurfaceView, it works perfectly but when scaling down or moving canvas it renders snapshots of scaling behind the canvas which is annoying.
So I want to clear my view background after onDraw() calls. I tried using view.setBackgroundColor() from the gameloop or inside onDraw() but android claims that I can use it from another thread.
Is there any equivalent method to do so?
public class GameLoopThread extends Thread
{
private MapView view;
private boolean running = false;
static final long FPS = 30;
public GameLoopThread(MapView view)
{ this.view = view; }
public void setRunning(boolean run)
{ running = run; }
#Override
public void run()
{
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running)
{
Canvas c = null;
startTime = System.currentTimeMillis();
try
{
c = view.getHolder().lockCanvas();
synchronized (view.getHolder())
{
view.onDraw(c);
view.setBackgroundColor(Color.WHITE); // fails on this
}
}
finally
{
if (c != null)
view.getHolder().unlockCanvasAndPost(c);
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try
{
if (sleepTime > 0) sleep(sleepTime);
else sleep(10);
}
catch (Exception e){}
}
}
}
Update
I don't want to use view.setBackgroundColor() instead I want to use something equivalent to draw behin the canvas
You can't update UI from a separate thread instead you can use runOnUiThread
You can only "Change" views from the UIThread. So a possible solution would be passing your Activity-Context and use
activityContext.runOnUiThread
Hope it helps
here it is better to use the Handler, because GameLoopThread has no Context
android.os.Handler handler = new android.os.Handler();
handler.post(new Runnable() {
#Override
public void run() {
//update here
}
});
make private MapView view; to final
You can update UI thread from handlers, asynctasks, or runOnUIThread method. You cannot do that from a normal thread
Thanks all for posting answers.
I have found my need which in its turn works as setBackgroundColor()
It's as simple as calling drawColor()
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE); // fill the whole canvas with white
// rest of drawing
}