I would like to animate movement on a SurfaceView . Ideally I would like to also be notified once the animation had finished.
For example:
I might have a car facing North. If I wanted to animate it so that it faces South for a duration of 500ms, how could I do that?
I am using a SurfaceView so all animation must be handled manually, I don't think I can use XML or the android Animator classes.
Also, I would like to know the best way to animate something continuously inside a SurfaceView (ie. a walk cycle)
Rotating images manually can be a bit of a pain, but here's how I've done it.
private void animateRotation(int degrees, float durationOfAnimation){
long startTime = SystemClock.elapsedRealtime();
long currentTime;
float elapsedRatio = 0;
Bitmap bufferBitmap = carBitmap;
Matrix matrix = new Matrix();
while (elapsedRatio < 1){
matrix.setRotate(elapsedRatio * degrees);
carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
//draw your canvas here using whatever method you've defined
currentTime = SystemClock.elapsedRealtime();
elapsedRatio = (currentTime - startTime) / durationOfAnimation;
}
// As elapsed ratio will never exactly equal 1, you have to manually draw the last frame
matrix = new Matrix();
matrix.setRotate(degrees);
carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
// draw the canvas again here as before
// And you can now set whatever other notification or action you wanted to do at the end of your animation
}
This will rotate your carBitmap to whatever angle you specify in the time specified + the time to draw the last frame. However, there is a catch. This rotates your carBitmap without adjusting its position on screen properly. Depending on how you're drawing your bitmaps, you could end up with your carBitmap rotating while the top-left corner of the bitmap stays in place. As the car rotates, the bitmap will stretch and adjust to fit the new car size, filling the gaps around it with transparent pixels. It's hard to describe how this would look, so here's an example rotating a square:
The grey area represents the full size of the bitmap, and is filled with transparent pixels. To solve this problem, you need to use trigonometry. It's a bit complicated... if this ends up being a problem for you (I don't know how you're drawing your bitmaps to the canvas so it might not be), and you can't work out the solution, let me know and I'll post up how I did it.
(I don't know if this is the most efficient way of doing it, but it works smoothly for me as long as the bitmap is less than 300x300 or so. Perhaps if someone knows of a better way, they could tell us!)
Do you want multiple independent animated object? If so, then you should use a game loop. (One master while loop that incrementally updates all game objects.) Here's a good discussion on various loop implementations. (I'm currently using "FPS dependent on Constant Game Speed" for my Android game project.)
So then your Car will look something like this (lots of code missing):
class Car {
final Matrix transform = new Matrix();
final Bitmap image;
Car(Bitmap sprite) {
image = sprite; // Created by BitmapFactory.decodeResource in SurfaceView
}
void update() {
this.transform.preRotate(turnDegrees, width, height);
}
void display(Canvas canvas) {
canvas.drawBitmap(this.image, this.transform, null);
}
}
You only need to load your bitmap once. So if you have multiple Cars, you may want to give them each the same Bitmap object (cache the Bitmap in your SurfaceView).
I haven't got into walk animations yet, but the simplest solution is to have multiple Bitmaps and just draw a different bitmap each time display is called.
Have a look at lunarlander.LunarView in the Android docs if you haven't already.
If you want to be notified when the animation is complete, you should make a callback.
interface CompletedTurnCallback {
void turnCompleted(Car turningCar);
}
Have your logic class implement the callback and have your Car call it when the turn's complete (in update()). Note that you'll get a ConcurrentModificationException if you are iterating over a list of Cars in update_game() and you try to remove a Car from that list in your callback. (You can solve this with a command queue.)
Related
I am trying to make an app, in which at the bottom corner is a mock timer with the seconds-hand(needle) going round and round like an old stop watch. My dial and needle are .png files drawn out in the "onDraw(Canvas canvas)", that is repeatedly called by my GameLoopThread(no problem here). The needle is rotated by separate anchor points which I have tested, this too works fine.
PROBLEM: when the new angle of the needle is calculated and is drawn, it appears on the top/left point of the surface view, possibly (0,0). while i need it at the bottom corner where i am drawing my dial!?
Heres my code
private void rotateCalc() {
matrix = new Matrix();
newAngle++;
matrix.postRotate(newAngle, 95, 10);//anchor points(95,10) for rotation
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(dialimg, null, dstbmp, null);
canvas.drawBitmap(needle, null,dstneedle,null);
rotateCalc();
canvas.drawBitmap(needle, matrix, null);
}
I guess its possibly because when using .drawBitmap method with matrix, i am not able to pass the Rect(demensions) or x,y (cordinates), that i would want the rotated Bitmap to be drawn to.
An idea how i may get around this?
Is this way inefficient when you compare with Creating a bitmap in which you can scale it and apply the matrix in the same function?
After that its the regular onDraw method on the new bitmap we have created
WOW! that works well. Heres my code, just checking if this sequence is fine? no particular logical order, right?
private void rotateCalc() {
matrix = new Matrix();
i++;
matrix.postRotate(i, 145, 8);
matrix.postScale(0.5f, 0.5f);
matrix.postTranslate(149, 337);
}
One last question, Im finding the Bitmaps to be a little choppy, as in.. I mean jagged, not really smooth. what can be done about that?
I've read to encorporate "Bitmap.Config.ARGB_8888", is that really needed? If so, could you give me an example. Thanx a lot #pskink, you've been a great help
EDIT
its very important to call matrix.postRotate(angle, x, y) ONLY after matrix.postScale(dx, dy), Because if you rotate first you will have to calculate the Anchor Points on the basis of the original Image you have chosen as your Bitmap. I preferred Scaling it, then finding out the new Width/Height and then deciding my anchor points.
Can't get android.graphics.picture objects to rotate, but translates are OK. No camera involved in the following code.
For making dynamic sprites, recording drawing primitives into an android.graphics.picture object for later use seems consistently more efficient than doing individual primitive draws during onDraw. And a Picture object is very similar to a bitmap, (if not the same?) and can be saved and restored to / from a bitmap.
However, just to be able to rotate these Pictures, I'd like to avoid having to save them as Bitmaps after recording() them. That is, just use the Picture objects like bitmaps, which the canvas api seems to imply is possible. Perhaps the Picture API is less sophisticated, but more likely I'm just doing this wrong.
The canvas API for Bitmaps versus Pictures are not parallel, e.g. there is a:
canvas.drawBitmap(bitmap_Sprite, matrix, null); // Draw sprite with matrix, no Paint
And there is a:
canvas.drawPicture(picture_Sprite, destination_rectangle); Draw sprite into rect.
But there isn't a:
canvas.drawPicture(picture_Sprite, matrix ...)
Details:
Imagine both bitmap_Sprite and picture_Sprite are a "compass arrow" indicating where it's pointing during rotation.
In the onDraw() call, using the bitmap version, this works:
matrix = new matrix();
matrix.setRotate(angle); // angle is ++ during onDraw(canvas)
// Draw arrow, matrix rotates it as expected:
canvas.drawBitmap(bitmap_Sprite, matrix, null); // null = no Paint obj
But attempting the same thing using the picture object, the only way I could figure this out was to rotate the the destination_rectangle (containing the arrow) like this:
//destination_rectangle sized correctly to wrap previously recorded picture obj.
matrix2 = new matrix();
matrix2.setRotate(angle); // angle is ++ during onDraw(canvas)
matrix2.mapRect(destination_rectangle); // apply rotation
canvas.drawPicture(picture_Sprite, destination_rectangle);
But all this does is pivot the rectangle around a location, it doesn't rotate the rectangle (and the arrow) itself.
Questions:
Is using the destination rect. the wrong way to do this?
Is rotation even possible using a Picture object?
Certainly, if I can't get Picture objects to rotate, then I could just use previously created bitmaps / mipmaps directly. So then
Generally, assuming it is possible to use Picture objects the same way as Bitmaps, am I really saving overhead using Pictures which are dynamically created once versus dynamically creating them, saving them as bitmaps, and reloading the bitmaps to do the matrix stuff?(!)
just use Canvas.concat, you will probably want save/restore as well
I got success in implementing a pinch zoom in/out and drag/drop functionality in images on the canvas.
Now what I want is re-sizing, that images like below link that based on iPhone App
How to change shape of an image using iPhone SDK?
So how can I achieve that kind of functionality in Android ?
Basically you need to invalidate the image and re-draw on the canvas from the beginning ::
img=(ImageView)findViewById(R.id.yourimageidfromxml);
img.onTouchEvent(MotionEvent me)
{
int X=me.getX();
int Y=me.getY();
img.invalidate();
img.repaint(X,Y);
}
void paint(int X,int Y)
{
img.setWidth(X);
img.setHeight(Y);
}
Scaling image will transform using repaint on canvas from the beginning
If by re-sizing you are referring to "stretching" the Bitmap on the vertical and horizontal plane then you simply modify the rect that the shape (eg. oval) is being drawn into.
For example:
This is your original shape of an oval:
canvas.drawOval(new Rect(0,0,100,100), bluePaint);
This is the same oval, just stretched (resized) on the horizontal plane:
canvas.drawOval(new Rect(0,0,200,100), bluePaint);
I hope this helps.
There are two options, both involving a custom view. The first is to create a custom view that fills your "canvas". You can keep track of 8 blue and 1 green circles in the view as Rect objects. Override onTouchEvent(MotionEvent) and then check if motion events are in any of your controls and update them accordingly (I'm simplifying things a bit here :)). From your onTouchEvent you would call invalidate(). You're onDraw(Canvas) can then handle drawing the controls and update the image according to how your controls have changed since the last call to onDraw.
The other option is to do something similar, but with a view that only encapsulates the circle and controls, meaning that moving the view around would require a container that will let the view change it's layout parameters. Doing this, your onTouchEvent method would need to trigger an invalidate with that layout view because it would need to recalculate the size and position of your view. This would definitely be harder but depending on what you are trying to achieve working with individual views may be better than maintaining representations of your circles in code in a single view.
The resizing of the image can be achieved by using a simple ImageView with scaleType "fitXY".
You have to add the blue resize handles yourself.
Changing the rotation of the image (green handle) can be achieved by using:
public static Bitmap rotate(Bitmap src, float degree) {
// create new matrix
Matrix matrix = new Matrix();
// setup rotation degree
matrix.postRotate(degree);
// return new bitmap rotated using matrix
return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
}
Source: http://xjaphx.wordpress.com/2011/06/22/image-processing-rotate-image-on-the-fly/
See http://xjaphx.wordpress.com/learning/tutorials/ for more Android Image Processing examples.
I am working with large size images and when I try to rotate them (applying matrix on the bitmap) a lot of seconds occurs.
I've seen that android system gallery can accomplish this task in a very fast manner instead. How is it possible?
I thought to perform the rotation on an asyncTask, applying only the ImageView rotation (which doesn't take long time) on the main thread, but if the app is killed before the asyncTask get to the end, the app fall in an inconsistent state.
This is my bitmap rotation code, which takes long time execution for large bitmaps:
Matrix mat = new Matrix();
mat.setRotate(90);
bMap = Bitmap.createBitmap(bMap, 0, 0, bMap.getWidth(), bMap.getHeight(), mat, true);
When doing modifications to a large image, create a low quality bitmap copy of it and use it for showing instant rotation or other editing effects. Once user is stopped motion (or left slider control) replace the low quality bitmap with the original image with same effects applied to it. This will significantly improve user experience. Try and let me know.
This might sound like a deviation from the original requirement- you can draw a rectangle instead of rotating the whole image in real time. In the end, what user needs is an indication of how much his image has rotated. This will be faster and can easily be done on UI thread without lag.
This is the simplest rotation on canvas code
canvas.save(); //save the position of the canvas
canvas.rotate(angle, X + (imageW / 2), Y + (imageH / 2)); //rotate the canvas
canvas.drawBitmap(imageBmp, X, Y, null); //draw the image on the rotated canvas
canvas.restore(); // restore the canvas position.
I am writing a View that should show a drawable that seems to "never end".
It should be twice or third the displaysize and move slow through the display.
Therefore I studied some samplecode by Google and found the important Lines
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
canvasWidth = width;
canvasHeight = height;
float sf = backgroundImage.getWidth() / canvasWidth;
backgroundImage = Bitmap.createScaledBitmap(backgroundImage,
(int) (canvasWidth * sf), canvasHeight, true);
}
To rescale the image and than
// decrement the far background
backgroundXPos = backgroundXPos - DELTAMOVE;
// calculate the wrap factor for matching image draw
int newFarX = backgroundImage.getWidth() - (-backgroundXPos);
// if we have scrolled all the way, reset to start
if (newFarX <= 0) {
backgroundXPos = 0;
// only need one draw
canvas.drawBitmap(backgroundImage, backgroundXPos, 0, null);
} else {
// need to draw original and wrap
canvas.drawBitmap(backgroundImage, backgroundXPos, 0, null);
canvas.drawBitmap(backgroundImage, newFarX, 0, null);
}
To draw the moving image. The images is already moving, it's fine.
But, and this is the point of my question, the image looks very ugly. Its original is 960*190 pixels by 240ppi. It should be drawn inside a view with 80dip of height and "fill_parent" width.
It should look same (and good) on all devices. I have tried a lot but I don't know how to make the picture look nice.
Thanks for your help.
Best regards,
Till
Since you're saying that it's a never ending drawable, probably you're writing a game of some sort. If your image is a pixel-art type, then you don't want any scaling; pixel-art-type images cannot be scaled and keep its crisp look (you can try using nearest neighbor interpolation and scaling to an integer multiple of the original, which sometimes might work, but sometimes you will still need manual tweaks). This is the rare case where you actually would need to have different image resource for different screen resolutions.
Otherwise you might want to use a vector image, but if -- as you said -- your original is a high resolution image, then vector image probably won't help much here.
btw, you probably want to show some screenshot. "Looks ugly" is just as helpful as saying my code does not work.
Just a guess, but instead of passing a null paint to your drawBitmap() calls, try making a paint with bitmap filtering disabled:
Paint p = new Paint();
p.setFilterBitmap(false);
canvas.drawBitmap(backgroundImage, backgroundXPos, 0, p);
Hope that helps.