I've played with the FlipImageView project and noticed one rather curious issue. When I'm running the sample contained within the project, everything works just fine. But, when I scale the FlipImageView manually (using View.setScaleX(float) and View.setScaleY(float) methods) this sample works fine just for devices with API version 16-18, and it clips the FlipImageView during the animation to the size of the unscaled image if sample is run on the device with the 19-th version of API. Here are screenshots:
The star is clipped, API 19
The star isn't clipped, API 18
I've set the visibility of all other widgets to GONE and changed the image's width to wrap_content to make an issue even more obvious. I see this behavior both on emulators and on real devices.
The code of the custom animation looks like so:
public class FlipAnimator extends Animation {
// fields declarations omitted
public void setToDrawable(Drawable to) {
toDrawable = to;
visibilitySwapped = false;
}
public FlipAnimator() {
setFillAfter(true);
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
camera = new Camera();
this.centerX = width / 2;
this.centerY = height / 2;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final double radians = Math.PI * interpolatedTime;
float degrees = (float) (180.0 * radians / Math.PI);
if(mIsRotationReversed){
degrees = -degrees;
}
if (interpolatedTime >= 0.5f) {
if(mIsRotationReversed){ degrees += 180.f; } else{ degrees -= 180.f; }
if (!visibilitySwapped) {
setImageDrawable(toDrawable);
visibilitySwapped = true;
}
}
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0.0f, 0.0f, (float) (150.0 * Math.sin(radians)));
camera.rotateX(mIsRotationXEnabled ? degrees : 0);
camera.rotateY(mIsRotationYEnabled ? degrees : 0);
camera.rotateZ(mIsRotationZEnabled ? degrees : 0);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
I've searched through the diff between API version 18 and 19 and didn't find any changes in the Matrix or Camera classes which are used in the Animation.applyTransformation() method. I didn't find any changes in the Animation class itself neither.
What is the source of this problem?
Upd: the clipping occurs only during the animation - when it ends, the FlipImageView is drawn in its full size.
Related
thank you for reading my question.
The problem I have only occurs in android 7.0 (Nougat) and what it's happening is that the card (a fragment with Image views) when it rotates in the animation, it suddenly crops part of the image, Look:
Android 7 Nougat Screenshoot
Card in rotation
Android 4.4 Kitkat Screenshoot
Card in rotation
I'm using Animation (not objectAnimator yet), Here's my code:
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// Angle around the y-axis of the rotation at the given time
// calculated both in radians and degrees.
final double radians = Math.PI * interpolatedTime;
float degrees = (float) (180.0 * radians / Math.PI);
Log.d(LOG_TAG," "+interpolatedTime);
// Once we reach the midpoint in the animation, we need to hide the
// source view and show the destination view. We also need to change
// the angle by 180 degrees so that the destination does not come in
// flipped around
if (interpolatedTime >= 0.5f) {
degrees -= 180.f;
fromView.setVisibility(View.GONE);
toView.setVisibility(View.VISIBLE);
}
if (forward)
degrees = -degrees; //determines direction of rotation when flip begins
//computing
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0, 0, Math.abs(degrees) * 3f);
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
Thank you so much for sharing your ideas. :)
Read the https://developer.android.com/reference/android/view/View.html#setCameraDistance(float)
You have to set the camera distance.
I know that this question is old, but I hope that will help somebody.
I am using this Rotate3dAnimation class to create a flipping coin animation which is also moving and scaling. But i can use it only with one image view. By just using startAnimation() method on that image view.
But What i want to do is, to use two sides of a coin so it will look like a real coin with two different sides is flipping. Can someone help me about how to do that?
Thanks
package com.example.movingcoin;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.graphics.Camera;
import android.graphics.Matrix;
/**
* An animation that rotates the view on the Y axis between two specified angles.
* This animation also adds a translation on the Z axis (depth) to improve the effect.
*/
public class Rotate3dAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
/**
* Creates a new 3D rotation on the Y axis. The rotation is defined by its
* start angle and its end angle. Both angles are in degrees. The rotation
* is performed around a center point on the 2D space, definied by a pair
* of X and Y coordinates, called centerX and centerY. When the animation
* starts, a translation on the Z axis (depth) is performed. The length
* of the translation can be specified, as well as whether the translation
* should be reversed in time.
*
* #param fromDegrees the start angle of the 3D rotation
* #param toDegrees the end angle of the 3D rotation
* #param centerX the X center of the 3D rotation
* #param centerY the Y center of the 3D rotation
* #param reverse true if the translation should be reversed, false otherwise
*/
public Rotate3dAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse) {
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
// camera.rotateY(degrees);
camera.rotateX(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
faced the same problem few days ago, found the solution in class FlipAnimator that you can find here: FlipAnimatorClass
it is pretty easy, actually: you just have to pass to FlipAnimator the two sides of the coin. The class is pretty easy to understand, I think, and it is practically doing what g00dy suggested in his comment above.
the trick is to rotate your view twice !
once from normal position to middle, change your view (eg. change the image of your coin) and then rotate it back from middle to normal.
you should do all the changes in view and starting rotation from middle to normal in the onAnimationEnd in AnimationListener of the first animation !
like this:
firstAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
findViewById(R.id.conceptsLay).setVisibility(View.GONE);
findViewById(R.id.factBaseLay).setVisibility(View.VISIBLE);
secondAnimation.startAnimation();
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
in the code above, I first rotate the conceptsLay to middle, where its basically invisible, then make it GONE and make the other examples view VISIBILE and start its animation from middle to normal!
so what the user sees is that the view is flipped !
needles to say that, first you will rotate it from 0 to 90, and then in secondAnimation, you will rotate it -90 to 0 !
also for making it smoother I've added some alpha animations as well!
hope it will help
I was trying to make a rotational cube using translations and rotations. I'm using a classe named Rotate3dAnimation.java provided in the Google samples. The rotation is working pretty well and i was working about making the illusion of depth on the cube while rotating making it go further and closer from the screen.
To do this i was modifying the method applyTransformation(float interpolatedTime, Transformation t) but the problem is that, the face of the cube (the one hiding while rotating) has to a movement of going further for the first half of the time, come close again at the second half.
Resuming:
Camera.translate(float x, float y, float z)
The z translate should be decreasing for the first half and increasing the second half.
Here is the code i'm using:
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final String orientation = mOrientation;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
float midTime = getDuration()/2;
if (mReverse) {
camera.translate(0.0f, 0.0f, (interpolatedTime<midTime)?
(mDepthZ * interpolatedTime): (mDepthZ * (1.0f - interpolatedTime)));
} else {
camera.translate(0.0f, 0.0f, (interpolatedTime<midTime)?
(mDepthZ * (1.0f - interpolatedTime)): (mDepthZ * interpolatedTime));
}
if(orientation.equals("horizontal"))
camera.rotateY(degrees);
else
camera.rotateX(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
But i dont think this is working very well... Any sugestions?
Thanks!
I actually had the same problem and after some debugging, I realized that interpolatedTime is not in milliseconds as I (and I am guessing you) expected. This is nice so you don't really have to calculate what the midTime really is, you just need to know the progress of the interpolation.
To check if you're halfway through with the animation just use
(interpolatedTime < .5)
in your ternary statement.
i have a button defined in xml i am adding animation to it (Card Flip 3D) its works fine in google nexus one but not working properly on Kindle's.
You can see a demo here Demo Card Flip Animation this is what i am trying to achive in ANDROID.
this is my code:
public class CardFlipAnimation extends Animation {
private Camera camera;
private View fromView;
private View toView;
private float centerX;
private float centerY;
private boolean forward = true;
private boolean visibilitySwapped;
/**
* Creates a 3D flip animation between two views. If forward is true, its
* assumed that view1 is "visible" and view2 is "gone" before the animation
* starts. At the end of the animation, view1 will be "gone" and view2 will
* be "visible". If forward is false, the reverse is assumed.
*
* #param fromView First view in the transition.
* #param toView Second view in the transition.
* #param centerX The center of the views in the x-axis.
* #param centerY The center of the views in the y-axis.
* #param forward The direction of the animation.
*/
public CardFlipAnimation(View fromView, View toView, int centerX, int centerY) {
this.fromView = fromView;
this.toView = toView;
this.centerX = centerX;
this.centerY = centerY;
setDuration(500);
setFillAfter(true);
setInterpolator(new AccelerateDecelerateInterpolator());
}
public void reverse() {
forward = false;
View temp = toView;
toView = fromView;
fromView = temp;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
camera = new Camera();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// Angle around the y-axis of the rotation at the given time. It is
// calculated both in radians and in the equivalent degrees.
final double radians = Math.PI * interpolatedTime;
float degrees = (float) (180.0 * radians / Math.PI);
// Once we reach the midpoint in the animation, we need to hide the
// source view and show the destination view. We also need to change
// the angle by 180 degrees so that the destination does not come in
// flipped around. This is the main problem with SDK sample, it does not
// do this.
if (interpolatedTime >= 0.5f) {
degrees -= 180.f;
if (!visibilitySwapped) {
fromView.setVisibility(View.GONE);
toView.setVisibility(View.VISIBLE);
visibilitySwapped = true;
}
}
if (forward)
degrees = -degrees;
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0.0f, 0.0f, (float) (150.0 * Math.sin(radians)));
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
in my activity i am doing like this:
CardFlipAnimation animator = new CardFlipAnimation(button, button,
button.getWidth() / 2, Button.getHeight() / 2);
if (button.getVisibility() == View.GONE) {
animator.reverse();
}
layout.startAnimation(animator); // Relative layout with the button
What is wrong with code it works fine in some devices and not work in some devices.Is nit giving me the 3d effect.
EDIT
This link solved my problem http://www.inter-fuser.com/2009/08/android-animations-3d-flip.html
Thanks to RobinHood
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.