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();
Related
I know how to use CircularReveal to reveal a view, so I'm looking for a way to do something like "CircularHide". In other words, I want to invisible a view by a circular animation (increasing radius) after making it visible. How can I do that?
I've written this to reveal:
private void startCircularReveal() {
RelativeLayout changeableLayout = findViewById(R.id.layoutChangeable);
int centerX = (likeButton.getRight() + likeButton.getLeft()) / 2;
int centerY = (likeButton.getBottom() + likeButton.getTop()) / 2;
float endRadius = (float) Math.hypot(changeableLayout.getWidth(), changeableLayout.getHeight());
changeableLayout.setVisibility(View.VISIBLE);
Animator revealAnimator = ViewAnimationUtils.createCircularReveal(changeableLayout,
centerX, centerY, 0, endRadius);
revealAnimator.setDuration(200).start();
}
I can't combine two TranslateAnimations, but if i run each animation separatly I have the expected results (works fine). My only problem is when trying to combine both TranslateAnimations in one AnimationSet, in this case no animation is rendered, and after the 4000 ms the image is rendered on top left of the parent view.
This is how I am trying to run both animations in a set:
private void animateView(int width, int height, View view) {
AnimationSet animationSet = new AnimationSet(true);
int startX1 = -(width / 2);
int startY1 = getView().getHeight();
int endX1 = getView().getWidth() / 3;
int endY1 = getView().getHeight() - (getView().getHeight() / 3);
int endX2 = getView().getWidth() - width / 2;
int endY2 = 0;
TranslateAnimation anim1 = new TranslateAnimation(startX1, endX1, startY1, endY1);
anim1.setDuration(2000);
animationSet.addAnimation(anim1);
TranslateAnimation anim2 = new TranslateAnimation(endX1, endX2, endY1, endY2);
anim2.setDuration(2000);
anim2.setStartOffset(2000);
animationSet.addAnimation(anim2);
view.startAnimation(animationSet);
}
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...
I tried to implement createCircularReveal() in FloatingButton. But the animation is too fast. How to increase the duration of the Animation. I tried setDuration(milli-seconds), But its not working.
I follow developer.android.com, Defining Custom Animations
Here my code:
int cx = (fabBtn.getLeft() + fabBtn.getRight()) / 2;
int cy = (fabBtn.getTop() + fabBtn.getBottom()) / 2;
int finalRadius = Math.max(fabBtn.getWidth(), fabBtn.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(fabBtn, cx, cy, 2, finalRadius);
anim.setDuration(2000);
fabBtn.setVisibility(View.VISIBLE);
anim.start();
I am also facing the issue as I mentioned and I am able to resolve this issue by getting cx and cy the center position for image view (In your case I think it is button).
So get cx and cy using this:
int cx = (fabBtn.getWidth()) / 2;
int cy = (fabBtn.getHeight()) / 2;
Instead of this::
int cx = (fabBtn.getLeft() + fabBtn.getRight()) / 2;
int cy = (fabBtn.getTop() + fabBtn.getBottom()) / 2;
You might be calling getWidth() and getHeight() too soon.
So you'll have to use a getViewTreeObserver()
Be sure to add a duration to anim.setDuration(time) and set the initial visibility of the view to INVISIBLE
Here the code:
public void checker() {
myView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
int finalHeight = myView.getMeasuredHeight();
int finalWidth = myView.getMeasuredWidth();
// Do your work here
myView.getViewTreeObserver().removeOnPreDrawListener(this);
cx = finalHeight / 2;
cy = finalWidth / 2;
finalRadius = Math.max(finalHeight, finalWidth);
anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
myView.setVisibility(View.VISIBLE);
anim.setDuration(3000);
anim.start();
return true;
}
});
}
I thought I had the same problem, but it seems that, for some reason, a duration of just 2000 milliseconds is not enough for the animation to show. When I set the duration to 3000, I saw a beautiful circle animation.
A small delay of 1 second also helped
// get the center for the clipping circle
int cx = myView.getWidth() / 2;
int cy = myView.getHeight() / 2;
// get the final radius for the clipping circle
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());
// create the animator for this view (the start radius is zero)
Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
anim.setStartDelay(1000);
// make the view visible when the animation starts
anim.addListener(new AnimatorListenerAdapter()
{
#Override
public void onAnimationStart(Animator animation)
{
super.onAnimationStart(animation);
myView.setVisibility(View.VISIBLE);
}
});
// start the animation
anim.start();
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;
}