I have an 800x480 image, and I want to have a scale animation so that the image scales to 960x576. I know that Android has a ScaleAnimation class for this, but I also want the user to be able to pause the animation while it's scaling and resume from where they paused.
How can I do this?
I did write some custom refresh loop but it's not super smooth like the ScaleAnimation class. The custom refresh loop just calculates the change in width and height that is desired per 25 ms and then sets the new width to the Rect. Then the loop calls invalidate.
//Reset the image size
x = (x + dx);
y = (y + dy);
imageWidth = (imageWidth + dWidth);
imageHeight = (imageHeight + dHeight);
I then draw on the canvas.
outputRect.set((int)(-x), (int)(-y), (int)imageWidth, (int)imageHeight);
canvas.drawBitmap(bitmap, srcRect, outputRect, paint);
How can I do this just as smoothly as with the Android OS or more efficiently?
I would suggest using Androids ScaleAnimation. You can anim.cancel() an animation and it might resume right where it was canceled if started again. The documentation suggest calling reset() before reusing an animation, so that might work.
If it does not work, you can save the animation-state on pause and create a new animation from that point on, if the user resumes the animation.
Related
I have a custom view that draws a few circles and about 10 arches that are constantly updating (rotation and size change). I am trying to animate this whole process, but I couldn't find any good practices for doing so under Canvas (I know the basics - use dp instead of px and so on), but I don't know hot to properly do the animation part.
Right now I'm iterating trough all of my objects, perform some calculations to determine the future position and draw them, but it looks choppy. Here is what I'm currently doing:
#Override
protected void onDraw(Canvas canvas) {
for(Arch arch : arches) {
arch.update();
canvas.drawArc(arch.getRect(), -arch.getCurrentRotation(), arch.getSweepAngle(), true, paint);
}
//logo.draw(canvas);
canvas.drawCircle(width / 2, height / 2, circle_size, paint_opaque);
logo.draw(canvas);
int textX = (int) (width / 2);
int textY = (int) ((height / 2) - ((paint_text.descent() + paint_text.ascent()) / 2));
canvas.drawText(text, textX, textY, paint_text);
invalidate();
}
There are few things wrong with your code.
You shouldn't be performing any heavy operations in the onDraw of your custom Views. That's one, really important principle of developing for android! Ideally inside your onDraw method you should ONLY draw. Sometimes you need to calculate something based on Canvas, but you should limit these sort of actions to the minimum. Everything you can preallocate and calculate somewhere else (not in onDraw), you should extract from your onDraw. In your case arch.update() (which I assume calculates the next "frame" of the animation) and calculation of your textX and textY should be moved somewhere else.
invalidate() basically means that you request for a redraw of your View. When a View is being redrawn, its onDraw will be called. That's why you really shouldn't be calling invalidate() in your onDraw! If every single onDraw requests for another onDraw, it causes a sort of an infinite loop to happen.
If you're trying to achieve a frame animation, you should be calculating all frame-related parameters in a separate thread that would wait some interval between updates, and would call invalidate() after every single one of them.
I'm hoping someone can help me out. I'm making an image manipulation app, and I found I needed a better way to load in large images.
My plan, is to iterate through "hypothetical" pixels of an image (a "for loop" that covers width/height of the base image, so each iteration represents a pixel), scale/translate/rotate that pixels position relative to the view, then use this information to determine which pixels are being displayed in the view itself, then use a combination of BitmapRegionDecoder and BitmapFactory.Options to load in only the section of image that the output actually needs rather than a full (even if scaled) image.
So far I seem to have covered scale of the image and translation properly, but I can't seem to figure out how to calculate rotation. Since it's not a real Bitmap pixel I can't use Matrix.rotate =( Here is the image translations in the onDraw of the view, imgPosX and imgPosY hold the center point of the image:
m.setTranslate(-userImage.getWidth() / 2.0f, -userImage.getHeight() / 2.0f);
m.postScale(curScale, curScale);
m.postRotate(angle);
m.postTranslate(imgPosX, imgPosY);
mCanvas.drawBitmap(userImage.get(), m, paint);
and here is the math so far of how I'm trying to determine if an images pixel is on the screen:
for(int j = 0;j < imageHeight;j++) {
for(int i = 0;i < imageWidth;i++) {
//image starts completely center in view, assume image is original size for simplicity
//this is the original starting position for each pixel
int x = Math.round(((float) viewSizeWidth / 2.0f) - ((float) newImageWidth / 2.0f) + i);
int y = Math.round(((float) viewSizeHeight / 2.0f) - ((float) newImageHeight / 2.0f) + j);
//first we scale the pixel here, easy operation
x = Math.round(x * imageScale);
y = Math.round(y * imageScale);
//now we translate, we do this by determining how many pixels
//our images x/y coordinates have differed from it's original
//starting point, imgPosX and imgPosY in the view start in center
//of view
x = x + Math.round((imgPosX - ((float) viewSizeWidth / 2.0f)));
y = y + Math.round((imgPosY - ((float) viewSizeHeight / 2.0f)));
//TODO need rotation here
}
}
so, assuming my math up until rotation is correct (probably not but it appears to be working so far), how would I then calculate the rotation from that pixels position? I've tried other similar questions like:
Link 1
Link 2
Link 3
without using rotation the pixels I expect to actually be on the screen are represented (I made text file that outputs the results in 1's and 0's so I can have a visual representation of whats on the screen), but with the formula found in those questions the information isn't what is expected. (Scenario: I've rotated an image so only the top left corner is visible in the view. Using the info from Here to rotate the pixel, I should expect to see a triangular set of 1's in the upper left corner of the output file, but that's not the case)
So, how would I calculate a a pixels position after rotation without using the Android matrix? But still get the same results.
And if I've just messed it up entirely my apologies =( Any help would be appreciated, this project has gone on for so long and I want to finally be done lol
If you need any more information I will provide as much as I possibly can =) Thank you for your time
I realize this question is particularly difficult so I will be posting a bounty as soon as SO allows.
You do not need to create your own Matrix, use the existing one.
http://developer.android.com/reference/android/graphics/Matrix.html
You can map bitmap coordinates to screen coordinates by using
float[] coords = {x, y};
m.mapPoints(coords);
float sx = coords[0];
float sy = coords[1];
If you want to map screen to bitmap coordinates, you can create the inverse matrix
Matrix inverse = new Matrix(m);
inverse.inverse();
inverse.mapPoints(...)
I think your overall approach is going to be slow, as doing the pixel manipulation on the CU from Java has a lot of overhead. When drawing bitmaps normally, the pixel manipulation is done on the GPU.
I looked at the H&M Android app and trying to figure out how to implement some widget.
Can anyone have an idea how this image frame is implemented?
I can guess that it using openGL.
A transparent png frame? Which could also be nine-patch!
I will venture a guess ;)
First the front image is created. In this case, it is built by inflating a linear layout with an ImageView and a TextView. Then this is cached to a Bitmap (at setup phase, not draw time).
In onDraw, that bitmap is drawn to the screen. Then the canvas is clipped to avoid drawing that area any more. Saves a lot of drawing time to not do a quadruple overdraw of transparent pixels.
Then the backgrounds are drawn like this:
for(int i = NUMBER_OF_LAYERS - 1; i > 0; i--) {
canvas.save();
float rotation = MAX_ANGLE * shiftModifier * ((float) i / (NUMBER_OF_LAYERS - 1));
canvas.rotate(rotation, mImageHalfWidth, mImageHalfHeight);
paint.setAlpha((int) (255f / (2 * i)));
canvas.drawRect(mBitmap.getBounds(), paint);
canvas.restore();
}
NUMBER_OF_LAYERS is the number of backgrounds layers.
MAX_ANGLE is the rotation angle for the most tilted layer.
shiftModifier is used to animate the background layers. It moves from zero (background completely hidden) to one (background angle = MAX_ANGLE).
paint is just a Paint with color set to white.
I have some sprites (Well, custom classes that implement Sprite, but whatever) that I resize. AndEngine resizes the image from the center, which makes an image placed at 0,0 no longer appear at 0,0. To fix this I applied
sprite.setScaleCenterX(0);
sprite.setScaleCenterY(0);
This places the image where I want it. However, now when I rotate the image, the image moves around (If the image were a plain square, rotating it should make no visible change). To fix this I applied
sprite.setRotationCenterX((sprite.getWidth() * sprite.getScaleX()) / 2);
sprite.setRotationCenterY((sprite.getHeight() * sprite.getScaleY()) / 2);
(For some reason, resizing a Sprite doesn't change the dimensions of the sprite, just the visual image, hence multiplying it by the scale). This, however, did not correct the problem, but merely changed where the image moved to when flipped.
Is my math off here? Wouldn't this center the rotation on the image so that the image doesn't move position? Or is there something else I'm missing?
Below is full code:
Sprite sprite = new Sprite(0, 0, singleTrackTR, getVertexBufferObjectManager());
sprite.setScale(scaleX, scaleY);
sprite.setScaleCenterX(0);
sprite.setScaleCenterY(0);
sprite.setRotationCenterX((sprite.getWidth() * sprite.getScaleX()) / 2);
sprite.setRotationCenterY((sprite.getHeight() * sprite.getScaleY()) / 2);
All your code is correct. I tried it myself, both the setProperty(x, y) and the setPropertyX/Y(a) versions.
By any chance, do you have it connected to a Body? Note that the Body also doesn't scale with a Sprite's setScale. It has its own setTransform method, which takes x and y (that you both have to divide by PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT) and a rotation value.
I know this has been discussed over and over again but I can't find a solution to suit my needs.
Scenario: To keep the explanation simple, I have a custom view which displays two images a big one for the background and a smaller one as a Wheel. The user can rotate/scale the background image with onTouch events. He also can rotate the Wheel image to make some operations. Everything is done on a custom View not SurfaceView because I need it to be transparent.
The problem: With onDraw() I always need to check what Rotation/Scale has the background image, scale/rotate it and then draw it. If the background image is smaller, let's say 512x512 the Wheel image rotation is fine. If the background image is bigger, 1280x1707, the wheel image rotation is laggy. So my guess is, manipulation and rotation of a big image in background, for each onDraw() gives me performance issues, when basically the background image should be redrawn only the the user manipulates it.
The rotation is done in something like:
canvas.save();
float dx = (maxX + minX) / 2;
float dy = (maxY + minY) / 2;
drawable.setBounds((int) minX, (int) minY, (int) maxX, (int) maxY);
canvas.translate(dx, dy);
canvas.rotate(angle * 180.0f / (float) Math.PI);
canvas.translate(-dx, -dy);
drawable.draw(canvas);
canvas.restore();
Possible solutions: I could make a new custom View which could only draw the background image and on top of it, put my current View and send touch events, when the case to my background image view. This will allow me to redraw the background image only when needed. Any other ideas ?
I was having similar performance problems and the solution I found is what I described here. Similar to what you already suggested, basically the background is only drawn once and the custom ImageView handles all transformations on its touch events.