Why does ScaleModifier make sprite flicker? - android

I'm playing around in AndEngine and learning this non-documented while making a splashscreen. I'm aware there's a class SplashScene, but as I'm learning I'm trying all kind of ways.
However, I can't seem to get this one right. The screen is 240x320 (W x H) and splash screen texture is 480x640 so i'm scaling it down to fit the screen. Texture loading etc is working fine, but when the sprite is shown I first see the large texture for 0.1secs, then it gets scaled down. I want it to get scaled down prior shown. Been trying everything, moved the call to attachChild() to onLoadComplete(), using setVisible(false), etc but I see the texture getting scaled down everytime.
Why?
Here's my code:
#Override
public Scene onLoadScene() {
this.scene = new Scene();
// Texture sizes
final int sX = mSplashTextureRegion.getWidth();
final int sY = mSplashTextureRegion.getHeight();
// Center on camera
final int cX = (CAMERA_WIDTH - sX) / 2;
final int cY = (CAMERA_HEIGHT - sY) / 2;
// Scale factor according to camera
final float scaleFactor = Math.min((float) CAMERA_WIDTH / sX, (float) CAMERA_HEIGHT / sY);
// Init sprite
splashScreen = new Sprite(cX, cY, mSplashTextureRegion);
splashScreen.setVisible(false);
// Rescale the splash-screen to fit the display, move it to (0, 0) and show it.
splashScreen.registerEntityModifier(new ScaleModifier(0.1f, 1.0f, scaleFactor));
//splashScreen.registerEntityModifier(new ScaleAtModifier(0.001f, 1.0f, scaleFactor, 0, 0));
// splashScreen.registerEntityModifier(new SequenceEntityModifier(
// new ScaleModifier(0.1f, 1.0f, scaleFactor),
// new DelayModifier(0.2f)
// ));
return scene;
}
#Override
public void onLoadComplete() {
scene.attachChild(splashScreen);
splashScreen.setVisible(true);
}
If I rewrite onLoadComplete() in to this:
#Override
public void onLoadComplete() {
scene.attachChild(splashScreen);
mHandler.postDelayed(korv, 1000);
}
private Runnable korv = new Runnable() {
#Override
public void run() {
splashScreen.setVisible(true);
}
};
the flicker is gone, but that doesn't feel like a good solution.

ScaleModifier adjust the scale over the specified time factor - in your case 0.1f
ScaleModifier(0.1f, 1.0f, scaleFactor)
What you probably want is to scale the Sprite directly using
SplashScreen.setScale(scaleFactor);
do that before attaching the child and you should be fine.

Related

Unity, Android: how to set the aspect/scale of the game properly

I've developed an Android game using Unity. And in the end I've faced the next problem:
Each level of my game has strict borders (like a box) along the sides of the screen. I've been developing the game with aspect 9:16 (pic. 1). When I've built it and run on my phone (it's 9:19.5) I found out that leftmost and rightmost parts of the levels are out of the screen (pic. 2).
How can I fix this, so the game fits every aspect ratio?
I've tried different solutions, but nothing satisfied me. I'm so desperate that I'm already agree to just scale it down from 9:16 so that it fits the WIDTH of the screen plus adding black bars at the top and at the bottom (pic. 3). Height is not so important in my game, but width is crucial. Or is there some better solution?
UPDATE 2:
My project's design (kinda grayboxing):
Here's how it looks FINE with different aspects in play mode:
And here's how it looks POORLY with different aspects in play mode:
Unity's camera assumes that games will be played in a landscape orientation. Therefore the amount of the game world that is rendered vertically will always be the same on any screen, regardless of aspect ratio. If you are restricting your game to be played only on mobile platforms, and only in portrait mode, then you will want to force Unity to do the opposite. You want the camera to always render the same width, and show a variable amount of height depending on aspect ratio. (Helpful hint: Develop with the smallest aspect ratio you expect, like 4:5, not 9:16. Configure all of the aspect ratios or resolutions you want to test in the dropdown menu at the top of the Game View. Switch between these for testing, and allow your UI and game view to expand out for taller aspect ratios. Otherwise your UI will probably run together on smaller aspect ratios. See my post on CanvasScaler here.)
Anyway, to change the default camera behavior, you'll need to add a script to your camera. Something like this should do:
Edit: I modified the component to handle both perspective and orthographic cameras. Based on your game it appears you are, or perhaps should be, using an orthographic camera, which my previous answer did not handle. Also, there are no more properties on the component. Simply attach to a camera, then use the camera's properties as normal. Also, it now handles viewports, so it works with cameras that don't occupy the entire screen.
There is one known issue. With an orthographic camera, if you attempt to adjust the viewport to be greater than the entire screen size, something internal to Unity resets the projection matrix and overrides this component's behavior. To fix, simply toggle the component, or reload the scene or editor, anything that triggers the component to do its thing again.
using UnityEngine;
[ExecuteAlways]
[RequireComponent(typeof(Camera))]
public class HorizontallyAlignedCamera : MonoBehaviour
{
private Camera _camera;
private float _aspectRatio;
private float _fieldOfView;
private bool _isOrthographic;
private float _orthographicSize;
private void Awake()
{
_camera = GetComponent<Camera>();
}
private void OnEnable()
{
CachePropertiesAndRecalculate();
}
private void LateUpdate()
{
if(CameraPropertyHasChanged())
CachePropertiesAndRecalculate();
}
private void OnDisable()
{
_camera.ResetProjectionMatrix();
}
private bool CameraPropertyHasChanged()
{
bool hasChanged = (_aspectRatio != _camera.aspect
|| _fieldOfView != _camera.fieldOfView
|| _isOrthographic != _camera.orthographic
|| _orthographicSize != _camera.orthographicSize);
return hasChanged;
}
private void CacheCameraProperties()
{
_aspectRatio = _camera.aspect;
_fieldOfView = _camera.fieldOfView;
_isOrthographic = _camera.orthographic;
_orthographicSize = _camera.orthographicSize;
}
private void CachePropertiesAndRecalculate()
{
CacheCameraProperties();
if(_camera.orthographic)
RecalculateOrthographicMatrix();
else
RecalculatePerspectiveMatrix();
}
private void RecalculatePerspectiveMatrix()
{
float near = _camera.nearClipPlane;
float nearx2 = near * 2.0f;
float far = _camera.farClipPlane;
float halfFovRad = _camera.fieldOfView * 0.5f * Mathf.Deg2Rad;
// This is what aligns the camera horizontally.
float width = nearx2 * Mathf.Tan(halfFovRad);
float height = width / _camera.aspect;
// This is the default behavior.
//float height = nearx2 * Mathf.Tan(halfFovRad);
//float width = height * _camera.aspect;
float a = nearx2 / width;
float b = nearx2 / height;
float c = -(far + near) / (far - near);
float d = -(nearx2 * far) / (far - near);
Matrix4x4 newProjectionMatrix = new Matrix4x4(
new Vector4(a, 0.0f, 0.0f, 0.0f),
new Vector4(0.0f, b, 0.0f, 0.0f),
new Vector4(0.0f, 0.0f, c, -1.0f),
new Vector4(0.0f, 0.0f, d, 0.0f));
_camera.projectionMatrix = newProjectionMatrix;
}
private void RecalculateOrthographicMatrix()
{
// This is what aligns the camera horizontally.
float width = 2.0f * _camera.orthographicSize;
float height = width / _camera.aspect;
// This is the default behavior.
//float height = 2.0f * _camera.orthographicSize;
//float width = height * _camera.aspect;
float near = _camera.nearClipPlane;
float far = _camera.farClipPlane;
float a = 2.0f / width;
float b = 2.0f / height;
float c = -2.0f / (far - near);
float d = -(far + near) / (far - near);
Matrix4x4 newProjectionMatrix = new Matrix4x4(
new Vector4(a, 0.0f, 0.0f, 0.0f),
new Vector4(0.0f, b, 0.0f, 0.0f),
new Vector4(0.0f, 0.0f, c, 0.0f),
new Vector4(0.0f, 0.0f, d, 1.0f));
_camera.projectionMatrix = newProjectionMatrix;
}
}
So, I found a way to achieve the goal.
I added an empty game object to the scene and child'ed to it all the level structure, plus adding two black bars (as simple cubes) above and below the level. With that approach I can scale that parent game object so that all the level structure scales proportionally.
All I have to do then is creating a script for scaling the parent game object according to the width of the screen.

handle aspect ratio in libgdx android

I am developing game with libgdx and i got stuck with aspect ratio on different devices.
After a lot of thinking i figured that following is best solution for the problem:
I want to have camera always fixed to 16:9 aspect ratio and draw everything using that camera
If a device aspect is for example 4:3 i want to show only part of the view, not to strech it.
it should look something like this
blue is virtual screen(camera viewport) and red is device screen(visible area to 4:3 devices)
If for example device screen is also 16:9 full view should be visible.
Problem is i don't know how to achieve this.
I've done this for portrait screens, leaving some blank spaces at the top and bottom. As you can see in the following picture, the game stays at a 4:3 ratio and leaves whatever is leftover blank.
The content is always centered, and keeps its aspect ratio by not stretching the content unevenly. So here is some sample code Im using to achieve it.
public static final float worldW = 3;
public static final float worldH = 4;
public static OrthographicCamera camera;
...
//CALCULATING THE SCREENSIZE
float tempCalc = Gdx.graphics.getHeight() * GdxGame.worldW / Gdx.graphics.getWidth();
if(tempCalc < GdxGame.worldH){
//Adjust width
camera.viewportHeight = GdxGame.worldH;
camera.viewportWidth = Gdx.graphics.getWidth() * GdxGame.worldH / Gdx.graphics.getHeight();
worldWDiff = camera.viewportWidth - GdxGame.worldW;
}else{
//Adjust Height
camera.viewportHeight = tempCalc;
camera.viewportWidth = GdxGame.worldW;
worldHDiff = camera.viewportHeight - GdxGame.worldH;
}
camera.position.set(camera.viewportWidth/2f - worldWDiff/2f, camera.viewportHeight/2f - worldHDiff/2f, 0f);
camera.zoom = 1f;
camera.update();
I'm sure im not proposing the perfect solution, but you can play with the values on how the camera position and viewports are calculated so you can achieve the desired effect. Better than nothing I guess.
Also, Clash Of The Olympians Developers talk about how to achieve something like it and still make it look good on different devices (it is really interesting, but there is no code though): Our solution to handle multiple screen sizes in Android - Part one
The newer versions of libGDX provides a wrapper for glViewport called Viewport.
Camera camera = new OrthographicCamera();
//the viewport object will handle camera's attributes
//the aspect provided (worldWidth/worldHeight) will be kept
Viewport viewport = new FitViewport(worldWidth, worldHeight, camera);
//your resize method:
public void resize(int width, int height)
{
//notice that the method receives the entire screen size
//the last argument tells the viewport to center the camera in the screen
viewport.update(width, height, true);
}
I have written a function which works with different sizes of screen and images. The image might be zoomed and translated if it's possible.
public void transformAndDrawImage(SpriteBatch spriteBatch, Texture background, float scl, float offsetX, float offseY){
float width;
float height;
float _offsetX;
float _offsetY;
float bh2sh = 1f*background.getHeight()/Gdx.graphics.getHeight();
float bw2sw = 1f*background.getWidth()/Gdx.graphics.getWidth();
float aspectRatio = 1f*background.getHeight()/background.getWidth();
if(bh2sh>bw2sw){
width = background.getWidth() / bw2sw * scl;
height = width * aspectRatio;
}
else{
height = background.getHeight() / bh2sh * scl;
width = height / aspectRatio;
}
_offsetX = (-width+Gdx.graphics.getWidth())/2;
_offsetX += offsetX;
if(_offsetX-Gdx.graphics.getWidth()<=-width) _offsetX=-width+Gdx.graphics.getWidth();
if(_offsetX>0) _offsetX=0;
_offsetY = (-height+Gdx.graphics.getHeight())/2;
_offsetY += offseY;
if(_offsetY-Gdx.graphics.getHeight()<=-height) _offsetY=-height+Gdx.graphics.getHeight();
if(_offsetY>0) _offsetY=0;
spriteBatch.draw(background, _offsetX, _offsetY, width, height);
}
How to use it? It's easy:
#Override
public void render(float delta) {
Gdx.graphics.getGL10().glClearColor( 0.1f, 0.1f, 0.1f, 1 );
Gdx.graphics.getGL10().glClear( GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT );
spriteBatch.setProjectionMatrix(cam.combined);
spriteBatch.begin();
//zoom the image by 20%
float zoom = 1.2f; //Must be not less than 1.0
//translating the image depends on a few parameters such as zooming, aspect ratio of screen and image
offsetX+=0.1f; //offset the image to the left, if it's possible
offsetY+=0.1f; //offset the image to the bottom, if it's possible
transformAndDrawImage(spriteBatch, background, zoom, offsetX, offsetY);
//draw something else...
spriteBatch.end();
}

android opengl check visibility of a point with camera zoom

I m woring on an android opengl 1.1 2d game with a top view on a vehicule and a camera zoom relative to the vehicule speed. When the speed increases the camera zoom out to offer the player a best road visibility.
I have litte trouble finding the exact way to detect if a sprite is visible or not regarding his position and the current camera zoom.
Important precision, all of my game's objects are on the same z coord. I use 3d just for camera effect. (that's why I do not need frustrum complicated calculations)
here is a sample of my GLSurfaceView.Renderer class
public static float fov_degrees = 45f;
public static float fov_radians = fov_degrees / 180 * (float) Math.PI;
public static float aspect; //1.15572 on my device
public static float camZ; //927 on my device
#Override
public void onSurfaceChanged(GL10 gl, int x, int y) {
aspect = (float) x / (float) y;
camZ = y / 2 / (float) Math.tan(fov_radians / 2);
Camera.MINIDECAL = y / 4; // minimum cam zoom out (192 on my device)
if (x == 0) { // Prevent A Divide By Zero By
x = 1; // Making Height Equal One
}
gl.glViewport(0, 0, x, y); // Reset The Current Viewport
gl.glMatrixMode(GL10.GL_PROJECTION); // Select The Projection Matrix
gl.glLoadIdentity(); // Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
GLU.gluPerspective(gl, fov_degrees, aspect , camZ / 10, camZ * 10);
GLU.gluLookAt(gl, 0, 0, camZ, 0, 0, 0, 0, 1, 0); // move camera back
gl.glMatrixMode(GL10.GL_MODELVIEW); // Select The Modelview Matrix
gl.glLoadIdentity(); // Reset The Modelview Matrix
when I draw any camera relative object I use this translation method :
gl.glTranslatef(position.x - camera.centerPosition.x , position.y -camera.centerPosition.y , - camera.zDecal);
Eveything is displayed fine, the problem comes from my physic thread when he checks if an object is visible or not:
public static boolean isElementVisible(Element element) {
xDelta = (float) ((camera.zDecal + GameRenderer.camZ) * GameRenderer.aspect * Math.atan(GameRenderer.fov_radians));
yDelta = (float) ((camera.zDecal + GameRenderer.camZ)* Math.atan(GameRenderer.fov_radians));
//(xDelta and yDelta are in reallity updated only ones a frame or when camera zoom change)
Camera camera = ObjectRegistry.INSTANCE.camera;
float xMin = camera.centerPosition.x - xDelta/2;
float xMax = camera.centerPosition.x + xDelta/2;
float yMin = camera.centerPosition.y - yDelta/2;
float yMax = camera.centerPosition.y + yDelta/2;
//xMin and yMin are supposed to be the lower bounds x and y of the visible plan
// same for yMax and xMax
// then i just check that my sprite is visible on this rectangle.
Vector2 phD = element.getDimToTestIfVisibleOnScreen();
int sizeXd2 = (int) phD.x / 2;
int sizeYd2 = (int) phD.y / 2;
return (element.position.x + sizeXd2 > xMin)
&& (element.position.x - sizeXd2 < xMax)
&& (element.position.y - sizeYd2 < yMax)
&& (element.position.y + sizeYd2 > yMin);
}
Unfortunately the object were disapearing too soon and appearing to late so i manuelly added some zoom out on the camera for test purpose.
I did some manual test and found that by adding approx 260 to the camera z index while calculating xDelta and yDelta it, was good.
So the line is now :
xDelta = (float) ((camera.zDecal + GameRenderer.camZ + 260) * GameRenderer.aspect * Math.atan(GameRenderer.fov_radians));
yDelta = (float) ((camera.zDecal + GameRenderer.camZ + 260)* Math.atan(GameRenderer.fov_radians));
Because it's a hack and the magic number may not work on every device I would like to understand what i missed. I guess there is something in that "260" magic number that comes from the fov or ration width/height and that could be set as a formula parameter for pixel perfect detection.
Any guess ?
My guess is that you should be using Math.tan(GameRenderer.fov_radians) instead of Math.atan(GameRenderer.fov_radians).
Reasoning:
If you used a camera with 90 degree fov, then xDelta and yDelta should be infinitely large, right? Since the camera would have to view the entire infinite plane.
tan(pi/2) is infinite (and negative infinity). atan(pi/2) is merely 1.00388...
tan(pi/4) is 1, compared to atan(pi/4) of 0.66577...

limit the area to move sprite by toucharea

I want to limit the area to move the sprite object only on this area (for example a area dimensions 200x200).
I would to create a box2D 200x200 where the sprites can moved only on this area
How do you do that please?
#Override
public Scene onCreateScene() {
this.mEngine.registerUpdateHandler(new FPSLogger());
final Scene scene = new Scene();
scene.setBackground(new Background(0.09804f, 0.6274f, 0.8784f));
final float centerX = (CAMERA_WIDTH - this.mFaceTextureRegionLetterOne
.getWidth()) / 2;
final float centerY = (CAMERA_HEIGHT - this.mFaceTextureRegionLetterOne
.getHeight()) / 2;
final Sprite letterOne = new Sprite(centerX - centerX / 2, centerY
- centerY / 2, this.mFaceTextureRegionLetterOne,
this.getVertexBufferObjectManager()) {
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent,
final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
this.setPosition(pSceneTouchEvent.getX() - this.getWidth() / 2,
pSceneTouchEvent.getY() - this.getHeight() / 2);
return true;
}
};
final Sprite letterTwo = new Sprite(centerX - centerX / 2, centerY,
this.mFaceTextureRegionLetterTwo,
this.getVertexBufferObjectManager()) {
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent,
final float pTouchAreaLocalX, final float pTouchAreaLocalY) {
this.setPosition(pSceneTouchEvent.getX() - this.getWidth() / 2,
pSceneTouchEvent.getY() - this.getHeight() / 2);
//int count = scene.getChildCount();
//for(int i = 0; i < count; i++) {
IEntity entity = scene.getChildByIndex(1);
if (entity instanceof Sprite) {
if (entity.getUserData().equals("sprite"))
if (((Sprite) entity).collidesWith(letterOne))
Log.v("colission", "face_box is collised on google plus -> letterTwo on letterOne");
}
//}
return true;
}
};
letterTwo.setUserData("sprite");
final Sprite boxArea = new Sprite(centerX, centerY,
this.mFaceTextureRegionBox, this.getVertexBufferObjectManager());
letterOne.setScale(2);
scene.attachChild(letterOne);
scene.registerTouchArea(letterOne);
letterTwo.setScale(2);
scene.attachChild(letterTwo);
scene.registerTouchArea(letterTwo);
boxArea.setScale(2);
scene.attachChild(boxArea);
scene.setTouchAreaBindingOnActionDownEnabled(true);
return scene;
}
Thank you.
I don't know box2D, but normally you would check the edges of the sprite on each movement, if they do not overlap the edges of the area.
If your sprite is represented by a Rectangle, and the area where you can move also represented by a Rectangle, this could be done easy.
But first, check the box2D API, maybe it has already some helper methods to ease this task, something like:
obect.overlaps(object)
You should create a Rectangle of 200X200 and then you should use create a body of the rectangle using the Box2D Physics. And for Fixture definition for rectangle you can set density, Elasticity and Friction so that your sprites will be treated accordingly when they collide with the boundary of the Rectangle.
To create a Rectangle you can refer the Examples of the Andengine.
With your code, you seem to try to do collision checking with your sprites ?!
It would be complicated and not nice when you try to do things without physics bodies. So lets use the physics bodies with your sprites.
But note that, DO NOT create a solid box body for your area (containing your sprites), lets use 4 separated body walls (left, top, right, bottom) to form a closed box; because game engine can only check collision with solid shapes.
The following is the code for reference:
/**
* #param pScene
* Sence of the game, get from class member
* #param pWorld
* physics world of the game, get from class member
*/
public void CreateSprites(final Scene pScene, final PhysicsWorld pWorld)
{
final FixtureDef mFixtureDef = PhysicsFactory.createFixtureDef(10, 1.1f, 0.0f);//should be placed as member of class
final Sprite letterTwo = new Sprite(centerX - centerX / 2, centerY,
this.mFaceTextureRegionLetterTwo,
this.getVertexBufferObjectManager());
final Body letterTwoBody = PhysicsFactory.createBoxBody(pWorld, letterTwo, BodyType.DynamicBody, mFixtureDef);
letterTwo.setUserData(letterTwoBody); // for later sprite-body attachment access
pScene.attachChild(letterTwo);
pWorld.registerPhysicsConnector(new PhysicsConnector(letterTwo, letterTwoBody, true, true));
}
/** Create the walls, in these boudaries sprites will move */
public void InitBoxWalls(Scene pScene, PhysicsWorld pWorld)
{
final float WALL_MARGIN_WIDTH = 5f;
final float WALL_MARGIN_HEIGHT = 10f;
final VertexBufferObjectManager vertexBufferObjectManager = this.getVertexBufferObjectManager();
mWallGround = new Rectangle(-WALL_MARGIN_WIDTH, mCameraHeight + WALL_MARGIN_HEIGHT - 2, mCameraWidth + 2*WALL_MARGIN_WIDTH, 2, vertexBufferObjectManager);
mWallRoof = new Rectangle(-WALL_MARGIN_WIDTH, -WALL_MARGIN_HEIGHT, mCameraWidth + 2*WALL_MARGIN_WIDTH, 2, vertexBufferObjectManager);
mWallLeft = new Rectangle(-WALL_MARGIN_WIDTH, -WALL_MARGIN_HEIGHT, 2, mCameraHeight + 2*WALL_MARGIN_HEIGHT, vertexBufferObjectManager);
mWallRight = new Rectangle(mCameraWidth + WALL_MARGIN_WIDTH - 2, -WALL_MARGIN_HEIGHT, 2, mCameraHeight + 2*WALL_MARGIN_HEIGHT, vertexBufferObjectManager);
PhysicsFactory.createBoxBody(pWorld, mWallGround, BodyType.StaticBody, mWallFixtureDef);
PhysicsFactory.createBoxBody(pWorld, mWallRoof, BodyType.StaticBody, mWallFixtureDef);
PhysicsFactory.createBoxBody(pWorld, mWallLeft, BodyType.StaticBody, mWallFixtureDef);
PhysicsFactory.createBoxBody(pWorld, mWallRight, BodyType.StaticBody, mWallFixtureDef);
pScene.attachChild(mWallGround);
pScene.attachChild(mWallRoof);
pScene.attachChild(mWallLeft);
pScene.attachChild(mWallRight);
}
And the last thing you should do is to look up example of Physics/MouseJoint in AndEngine examples.

How to animate zoom out with ImageView that uses Matrix scaling

So I have an ImageView using a Matrix to scale the Bitmap I'm displaying. I can double-tap to zoom to full-size, and my ScaleAnimation handles animating the zoom-in, it all works fine.
Now I want to double-tap again to zoom out, but when I animate this with ScaleAnimation, the ImageView does not draw the newly exposed areas of the image (as the current viewport shrinks), instead you see the portion of visible image shrinking in. I have tried using ViewGroup.setClipChildren(false), but this only leaves the last-drawn artifacts from the previous frame - leading to an trippy telescoping effect, but not quite what I was after.
I know there are many zoom-related questions, but none cover my situation - specifically animating the zoom-out operation. I do have the mechanics working - ie aside from the zoom-out animation, double-tapping to zoom in and out works fine.
Any suggestions?
In the end I decided to stop using the Animation classes offered by Android, because the ScaleAnimation applies a scale to the ImageView as a whole which then combines with the scale of the ImageView's image Matrix, making it complicated to work with (aside from the clipping issues I was having).
Since all I really need is to animate the changes made to the ImageView's Matrix, I implemented the OnDoubleTapListener (at the end of this post - I leave it as an "exercise to the reader" to add the missing fields and methods - I use a few PointF and Matrix fields to avoid excess garbage creation). Basically the animation itself is implemented by using View.post to keep posting a Runnable that incrementally changes the ImageView's image Matrix:
public boolean onDoubleTap(MotionEvent e) {
final float x = e.getX();
final float y = e.getY();
matrix.reset();
matrix.set(imageView.getImageMatrix());
matrix.getValues(matrixValues);
matrix.invert(inverseMatrix);
doubleTapImagePoint[0] = x;
doubleTapImagePoint[1] = y;
inverseMatrix.mapPoints(doubleTapImagePoint);
final float scale = matrixValues[Matrix.MSCALE_X];
final float targetScale = scale < 1.0f ? 1.0f : calculateFitToScreenScale();
final float finalX;
final float finalY;
// assumption: if targetScale is less than 1, we're zooming out to fit the screen
if (targetScale < 1.0f) {
// scaling the image to fit the screen, we want the resulting image to be centred. We need to take
// into account the shift that is applied to zoom on the tapped point, easiest way is to reuse
// the transformation matrix.
RectF imageBounds = new RectF(imageView.getDrawable().getBounds());
// set up matrix for target
matrix.reset();
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
matrix.postScale(targetScale, targetScale);
matrix.mapRect(imageBounds);
finalX = ((imageView.getWidth() - imageBounds.width()) / 2.0f) - imageBounds.left;
finalY = ((imageView.getHeight() - imageBounds.height()) / 2.0f) - imageBounds.top;
}
// else zoom around the double-tap point
else {
finalX = x;
finalY = y;
}
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final long startTime = System.currentTimeMillis();
final long duration = 800;
imageView.post(new Runnable() {
#Override
public void run() {
float t = (float) (System.currentTimeMillis() - startTime) / duration;
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = interpolator.getInterpolation(t);
float tempScale = scale + interpolatedRatio * (targetScale - scale);
float tempX = x + interpolatedRatio * (finalX - x);
float tempY = y + interpolatedRatio * (finalY - y);
matrix.reset();
// translate initialPoint to 0,0 before applying zoom
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
// zoom
matrix.postScale(tempScale, tempScale);
// translate back to equivalent point
matrix.postTranslate(tempX, tempY);
imageView.setImageMatrix(matrix);
if (t < 1f) {
imageView.post(this);
}
}
});
return false;
}

Categories

Resources