How to apply easing animation function on view in Android - android

I want to apply a translate animation on an Android view (button) using a custom interpolator where the easing function is:
public static float easeOut(float t,float b , float c, float d) {
if ((t/=d) < (1/2.75f)) {
return c*(7.5625f*t*t) + b;
} else if (t < (2/2.75f)) {
return c*(7.5625f*(t-=(1.5f/2.75f))*t + .75f) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625f*(t-=(2.25f/2.75f))*t + .9375f) + b;
} else {
return c*(7.5625f*(t-=(2.625f/2.75f))*t + .984375f) + b;
}
}
I have an example that uses the custom interpolator like this:
The interpolator is:
public class HesitateInterpolator implements Interpolator {
public HesitateInterpolator() {
}
public float getInterpolation(float t) {
float x = 2.0f * t - 1.0f;
return 0.5f * (x * x * x + 1.0f);
}
}
and is used like this:
ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f);
anim.setInterpolator(new HesitateInterpolator());
My question is:
What are these values b, c, d for?

FYI: for people who just want an ease interpolator you can just use myAnimator.setInterpolator(new AccelerateDecelerateInterpolator());

I made a library which can solve this problem. AndroidEasingFunctions

1,2,3 go
Create a custom cubic bezier curve using this awesome site. And get the control points for the curve. Between 0,0 and 1,1
Interpolator customInterpolator = PathInterpolatorCompat.create(cpX1,cpX2,cpY1,cpY2)
Add this customInterpolator to any of your animation.
Added a gist.
Some more here.

According to Robert Penner's Easing Functions, as stated here:
t: current time, b: begInnIng value, c: change In value, d: duration
If you want to implement your custom Interpolator, you have to make something like this:
(this would be the implementation for the easeInOutQuint)
public class MVAccelerateDecelerateInterpolator implements Interpolator {
// easeInOutQuint
public float getInterpolation(float t) {
float x;
if (t<0.5f)
{
x = t*2.0f;
return 0.5f*x*x*x*x*x;
}
x = (t-0.5f)*2-1;
return 0.5f*x*x*x*x*x+1;
}
}
Edit:
to implement the easing function you need some math knowledge, considering that the getInterpolation method gets only the t parameter, from 0.0 to 1.0.
So basically you need to develop a y(t) function, with t from 0 to 1, and with y values from 0 to 1, as shown below:
What you change is the curve to get from 0 to 1 (in the image the green line is the linear one, for example). You need to 'normalize' the easing functions to remain in the (0, 1) x (0, 1) square, as you can see in my easeInOutQuint implementation.

Here is another solution, made possible by a recent addition to android's support library:
https://gist.github.com/ebabel/8ff41cad01e9ce1dd9ce
and an example in use:
public static void expand(final View v) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int targetHeight = v.getMeasuredHeight();
if ( v.getHeight() != targetHeight ) {
// Older versions of android (pre API 21) cancel animations for views with a height of 0 so use 1 instead.
v.getLayoutParams().height = 1;
v.setVisibility(View.VISIBLE);
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setInterpolator(EasingsConstants.easeInOutQuart);
a.setDuration(computeDurationFromHeight(v));
v.startAnimation(a);
} else {
Log.d("AnimationUtil", "expand Already expanded ");
}
}
/**
* 1dp/ms * multiplier
*/
private static int computeDurationFromHeight(View v) {
return (int) (v.getMeasuredHeight() / v.getContext().getResources().getDisplayMetrics().density) * DURATION_MULTIPLIER;
}

Related

How to spin bitmap along with Circle ProgressBar?

I am trying to modify a little this library: https://github.com/Shinelw/ColorArcProgressBar
And this is what i've done so far, without the purple circle indicator:
arc progress bar
My question is: how can i animate the purple circle along with the progress(as shown in the image)?
Inside ColorArcProgressBar class that extends View, at onDraw method, this is how the progress is draw: canvas.drawArc(bgRect, startAngle, currentAngle, false, progressPaint);
and the animation is
private void setAnimation(float last, float current, int length) {
progressAnimator = ValueAnimator.ofFloat(last, current);
progressAnimator.setDuration(length);
progressAnimator.setTarget(currentAngle);
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
currentAngle = (float) animation.getAnimatedValue();
curValues = currentAngle / k;
}
});
progressAnimator.start();
}
I managed to position the purple bitmat at the start of the progress like this:
canvas.drawBitmap(mIcon,bgRect.left+mIcon.getWidth()/2 +30, bgRect.bottom - 40 +mIcon.getHeight()/2 , null);
Now, how can I animate it along the arc like the progress?
Didn't try this out, if it's not right it's close...
// since currentAngle is a "sweep" angle, the
// final angle should be current + start
float thetaD = startAngle + currentAngle;
if (thetaD > 360F) {
thetaD -= 360F;
}
// convert degrees to radians
float theta = (float) Math.toRadians(thetaD);
// polar to Cartesian coordinates
float x = (float) (Math.cos(theta) * bgRect.width() / 2) + bgRect.centerX();
float y = (float) (Math.sin(theta) * bgRect.height() / 2) + bgRect.centerY();
canvas.drawBitmap(mIcon, x - mIcon.getWidth()/2, y - mIcon.getHeight()/2 , null);
It helped that your bitmap is circular, so we didn't have to rotate that...

Android meaningful transitions example

How to implement this animation using curvedmotion because documentation does not have a good example.
https://www.google.com/design/spec/animation/meaningful-transitions.html#meaningful-transitions-hierarchical-timing
As you can see, the path is basically a quarter of an oval. The parametric equation of an oval is like this:
x = a * cos(t)
y = b * cos(t)
See http://www.mathopenref.com/coordparamellipse.html
You have three parameters. Two of them (a, b) are the width and the height of the bounding box of your quarter-oval. The third one can be taken from animation interpolator. To get the right quarter, you need a value from range [PI*3/2, PI*2].
final View fab;
final int startX = fab.getTranslationX(),startY = fab.getTranslationY();
final int endX = 100,endY = 0;
ValueAnimator animator = ValueAnimator.ofFloat((float)(Math.PI*3/2),(float)(Math.PI*2));
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(500);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float t = (float) valueAnimator.getAnimatedValue();
float a = endX-startX;
float b = endY-startY;
fab.setTranslationX((float) (a*Math.cos(t)));
fab.setTranslationY((float) (b*Math.sin(t)));
}
});
animator.start();

How to achieve uniform velocity throughout a Translate Animation?

I have an infinite translate animation applied to an ImageView:
Animation animation = new TranslateAnimation(0, 0, -500, 500);
animation.setDuration(4000);
animation.setFillAfter(false);
myimage.startAnimation(animation);
animation.setRepeatCount(Animation.INFINITE);
What I have noticed is that the translation process is slower when the image is near the starting and ending point compared to when its near half the distance (middle point).
I guess the velocity for translate animations on android is not uniform.
How do I make the velocity uniform throughout the process ?
I did some source-diving to investigate this. First, note that if a linear interpolator is used to provide interpolatedTime values to the applyTransformation method of TranslateAnimation, the resulting translation will have constant velocity (because the offsets dx and dy are linear functions of interpolatedTime (lines 149-160)):
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}
applyTransformation is called by the getTransformation method of the base Animation class (lines 869-870):
...
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
...
According to the documentation for the setInterpolator method (lines 382-392), mInterpolator should default to a linear interpolator:
/**
* Sets the acceleration curve for this animation. Defaults to a linear
* interpolation.
*
* #param i The interpolator which defines the acceleration curve
* #attr ref android.R.styleable#Animation_interpolator
*/
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
However, this seems to be false: both constructors in the Animation class call the ensureInterpolator method (lines 803-811):
/**
* Gurantees that this animation has an interpolator. Will use
* a AccelerateDecelerateInterpolator is nothing else was specified.
*/
protected void ensureInterpolator() {
if (mInterpolator == null) {
mInterpolator = new AccelerateDecelerateInterpolator();
}
}
which suggests that the default interpolator is an AccelerateDecelerateInterpolator. This explains the behavior you describe in your question.
To actually answer your question, then, it would appear that you should amend your code as follows:
Animation animation = new TranslateAnimation(0, 0, -500, 500);
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(4000);
animation.setFillAfter(false);
myimage.startAnimation(animation);
animation.setRepeatCount(Animation.INFINITE);

Drawable animation with AnimatorSet not working - drawable disappears

I'm trying to develop simple board game. The board is of size 9x9 fields. The balls are appearing on the fields and when the user clicks on the field with the ball, the ball starts to jumping. I implemented the animation in two ways. The first one is working, but it's not easy to add another one following animation (like little stretch or something). And the second one, which seems to be better (there is used the AnimatorSet) is not working. When user clicks on the field with the ball, the ball disappears. I have no idea why :-(.
The first class implements the board and it is the child of View:
public class BoardView extends View {
...
/**
* Initializes fields of the board.
*/
private void initializeFields() {
this.fields = new ArrayList<Field>();
for (int row = 0; row < BoardView.FIELDS_NUMBER; row++) {
for (int column = 0; column < BoardView.FIELDS_NUMBER; column++) {
this.fields.add(new Field(this, row, column));
}
}
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(BoardView.COLOR_ACTIVITY);
if (this.fields == null) {
this.initializeFields();
}
for (int i = 0; i < this.fields.size(); i++) {
this.fields.get(i).draw(canvas);
}
}
...
}
The second one implements the field:
public class Field {
...
/**
* Draws itself on the screen.
*
* #param Canvas canvas
*/
public void draw(Canvas canvas) {
Rect field = this.getRect();
int round = (int)Math.floor(this.board.getFieldSize() / 4);
this.board.getPainter().setStyle(Paint.Style.FILL);
this.board.getPainter().setColor(Field.COLOR_DEFAULT);
// draw field
canvas.drawRoundRect(new RectF(field), round, round, this.board.getPainter());
// draw selected field
if (this.selected) {
this.board.getPainter().setColor(Field.COLOR_SELECTED);
canvas.drawRoundRect(new RectF(field), round, round, this.board.getPainter());
}
// draw ball
if (this.ball != null) {
Point fieldOrigin = new Point(field.left, field.top);
if (this.selected) {
this.ball.animate(canvas, fieldOrigin);
} else {
this.ball.draw(canvas, fieldOrigin);
}
}
}
...
}
And the last one implements the ball:
Here is the first method, which completely works, but it's not flexible enough:
public class Ball {
...
/**
* Draws itself on the screen.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void draw(Canvas canvas, Point fieldOrigin) {
// set painter
Paint painter = this.field.getBoard().getPainter();
painter.setStyle(Paint.Style.FILL);
painter.setColor(Ball.COLORS[this.color]);
// calculate parameters
float halfSize = this.field.getBoard().getFieldSize() / 2;
float cX = fieldOrigin.x + halfSize;
float cY = fieldOrigin.y + halfSize + this.dy;
float radius = 0.6f * halfSize;
// draw circle
canvas.drawCircle(cX, cY, radius, painter);
// the code continues, because of the shadow and light simulation (radial gradients)
}
/**
* Draws jumping animation.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void animate(Canvas canvas, Point fieldOrigin) {
float currentDy = (this.dy - 0.1f);
this.setDy((float)Math.abs(Math.sin(currentDy)) * (-0.15f * this.field.getBoard().getFieldSize()));
this.draw(canvas, fieldOrigin);
this.setDy(currentDy);
try {
Thread.sleep(Ball.ANIMATION_DELAY);
} catch (InterruptedException e) {}
this.field.invalidate();
}
...
}
As you can see, the animation is implemented by sleeping the current Thread and changing parameter dy.
The second method is showing the ball on the field, but the animation is not working as I said in the beginning of the post (after click, the ball disappears):
public class BallShape {
private Field field;
private LayerDrawable ball;
private int color;
private float diameter,
x, y; // top left corner - THE GETTERS AND SETTERS ARE IMPLEMENTED (because of Animator)
...
/**
* Initializes the ball.
*
* #param Field field
* #param int color
*/
public BallShape(Field field, int color) {
this.field = field;
this.color = ((color == Ball.COLOR_RANDOM) ? Ball.randomColor() : color);
// create ball
float halfSize = this.field.getBoard().getFieldSize() / 2;
this.diameter = 0.6f * field.getBoard().getFieldSize();
float radius = this.diameter / 2;
Rect fieldArea = field.getRect();
this.x = fieldArea.left + halfSize - radius;
this.y = fieldArea.top + halfSize - radius;
// color circle
OvalShape circleShape = new OvalShape();
circleShape.resize(this.diameter, this.diameter);
ShapeDrawable circle = new ShapeDrawable(circleShape);
this.initPainter(circle.getPaint());
// the code continues, because of the shadow and light simulation (radial gradients)
// compound shape - ball
ShapeDrawable[] compound = { circle };//, shadow, light };
this.ball = new LayerDrawable(compound);
}
/**
* Draws itself on the screen.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void draw(Canvas canvas, Point fieldOrigin) {
canvas.save();
canvas.translate(this.x, this.y);
this.ball.draw(canvas);
canvas.restore();
}
/**
* Draws jumping animation.
*
* #param Canvas canvas
* #param Point fieldOrigin
*/
public void animate(Canvas canvas, Point fieldOrigin) {
// common data
float halfSize = this.field.getBoard().getFieldSize() / 2;
float radius = this.diameter / 2;
float startY = fieldOrigin.y + halfSize - radius;
float endY = startY - halfSize + 2;
// bounce animation
ValueAnimator bounceAnimation = ObjectAnimator.ofFloat(this, "y", startY, endY);
bounceAnimation.setDuration(BallShape.ANIMATION_LENGTH);
bounceAnimation.setInterpolator(new AccelerateInterpolator());
bounceAnimation.setRepeatCount(ValueAnimator.INFINITE);
bounceAnimation.setRepeatMode(ValueAnimator.REVERSE);
//bounceAnimation.start();
// animation
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnimation);
// start the animation
bouncer.start();
}
...
}
Any idea why it's not working? What I've done wrong?
Thank you very, very much.
Two things I would fix.
First of all you start animation in draw() method. You should either start it in onClick() or at least set this.selected to false, to not start it on every draw(). Secondly, after your value animator changes a property, you need to redraw the BallShape. Otherwise nothing will change. For instance you can define setY(float Y) method, change Y there and call invalidate().

Card filp Animation to button not working on some devices

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

Categories

Resources