I am wanting to hide an extended fab when it is clicked using an Animation. I want to scale the button down from original height and width to 0/gone. It disappears in the corner but the animation is laggy in the middle.
private fun animateHideScoreFab(){
val animX = ValueAnimator.ofInt(editScoreFab.measuredWidth, 0).apply {
duration = 1000
}
val animY = ValueAnimator.ofInt(editScoreFab.measuredHeight, 0).apply {
duration = 1000
}
animX.addUpdateListener {
val value = it.animatedValue as Int
editScoreFab.layoutParams.width = value
editScoreFab.requestLayout()
}
animY.addUpdateListener {
val value = it.animatedValue as Int
editScoreFab.layoutParams.height = value
}
AnimatorSet().apply {
this.interpolator = AccelerateDecelerateInterpolator()
play(animX).with(animY)
start()
}
}
Result:
Using AccelerateDecelerateInterpolator https://developer.android.com/reference/android/view/animation/AccelerateDecelerateInterpolator says
An interpolator where the rate of change starts and ends slowly but accelerates through the middle.
So in the middle the rate of change is faster than the start which could make it look laggy
Try another Interpolator like LinearInterpolator https://developer.android.com/reference/android/view/animation/LinearInterpolator.html which has a constant rate of change.
Update
For AccelerateDecelerateInterpolator if you look at the table in https://developer.android.com/guide/topics/graphics/prop-animation.html#interpolators between 400ms and 600ms the the value jumps 45.5% of the distance you and animating
The other factor of smoothness is not to use an Int but use a Float e.g. ValueAnimator.ofFloat so it has intermediate steps,
Update2
Re-laying out an item is expensive as it has to be measured and redrawn.
It should be faster and smoother just to scale the already drawn image as this is usually done by the GPU and thus faster and smoother. Also scaling a view takes a Float
example of scaling a Fab to top right onClick (Note using ObjectAnimator as it is simpler to implement)
Sorry in Java not Kotlin
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
AnimatorSet animationSet = new AnimatorSet();
// Set point to scale around to top right
view.setPivotY(0);
view.setPivotX(view.getMeasuredWidth());
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view,"scaleY", 1f, 0f);
scaleY.setDuration(1000);
// Optional as AccelerateDecelerateInterpolator is the default
scaleY.setInterpolator(new AccelerateDecelerateInterpolator());
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view,"scaleX", 1f, 0f);
scaleX.setDuration(1000);
// Optional as AccelerateDecelerateInterpolator is the default
scaleX.setInterpolator(new AccelerateDecelerateInterpolator());
animationSet.playTogether(scaleX, scaleY);
animationSet.start();
}
});
There's a scaleX and scaleY property in Views that you can interpolate from 1 to 0, this will give the effect of the View shrinking and not the container.
Second, for a scale animation work to the top if your parent layout params doesn't do for you, you have to set the pivot to the rightXtop corner with: view.pivotX = view.width; view.pivotY = 0
Also, if you using propertyListener and updating it yourself you can use only one ValueAnimator and do the calc yourself plus consider replacing editScoreFab.requestLayout() with editScoreFab.invalidate()
Related
How can I create an animation similar to the stock android launcher animation applied to apps when you change homescreen pages.
Here's a gif: https://i.stack.imgur.com/Zh7qE.gif
As the page swipes, the icons slightly overshoot their mark, and settle back to the center. I don't see how I can do that with a PageTransformer and I can't find any resources to point me in the right direction.
I once created a one-time bounce animation:
val pixels = binding.pager.width / 8
ValueAnimator.ofInt(0, pixels).apply {
duration = 200L
interpolator = DecelerateInterpolator()
repeatCount = 3
repeatMode = ValueAnimator.REVERSE
addUpdateListener {
binding.pager.scrollX = it.animatedValue as Int
}
}.start()
Use ofInt(0, pixels) for bouncing left or ofInt(0, -pixels) to bouncing right.
In my code I used a dragging distance of 1/8 of the view pager.
Feel free to choose how much you want to bounce:
val pixels = PIXELS_TO_DRAG
Finally, the repeatCount determines how many bounces - use 3 for 2 bounces, 5 for 3 bounces, 7 for 4 bounces etc.
You can fire this animation each time a pager transition end and tweak the parameters to get the desired behavior.
Enjoy,
Hope it helps :)
According to #Shlomi, you can do this implementation:
var currentPage = 0
binding.viewPager.onPageChangeListener {
val pixels = binding.vpBennefits.width / 8
ValueAnimator.ofInt(
0, if (currentPage < it)
pixels
else
-pixels
).apply {
duration = 200L
interpolator = DecelerateInterpolator()
repeatCount = 1
repeatMode = ValueAnimator.REVERSE
addUpdateListener {
binding.vpBennefits.scrollX = it.animatedValue as Int
}
}.start()
currentPage = it
}
By this way you can obtain the desired effect in both sides
A think a do not understand enough matrices theory to build animation for matrix inside image view.
For example i have some value that i want to set in postScale method to my matrix.
I use this code
fun animateScale(scale_factor:Float,matrix: Matrix)
{
matrix.postScale(scale_factor,scale_factor)
image_view.imageMatrix = matrix
}
This code makes scale to image view as it is expected. But i cant calculate postScale with animation. I tried different approaches using ValueAnimator.ofFloat(0f, 1f) or ValueAnimator.ifInt(1, 100) but result is weird.
Updated
I added my tries of animating as it asked in comments.
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.addUpdateListener(
{ animation ->
val current_anim_value = animation.animatedValue as Float
val factor_to_anim = scale_factor - ( scale_factor * current_anim_value)
matrix.postScale(factor_to_anim, factor_to_anim)
image_view.imageMatrix = matrix
})
animator.duration = 3000
animator.start()
I am trying to play animation (like drop or bounce) on placing of an 3D object (.sfb in my case), but I don't have any idea how can I do that.
I have tried to use ValueAnimator that is working fine for changing its color value, but that's not I wanted.
val alphaAnimator = ValueAnimator.ofFloat(0.0f, 1.0f, 0.0f)
alphaAnimator.addUpdateListener { animation ->
val animatedAlpha = animation.animatedValue as Float
modelRenderable.material.setFloat4(
MaterialFactory.MATERIAL_COLOR,
Color(0.0f, 0.0f, 1.0f * animatedAlpha, animatedAlpha)
)
}
alphaAnimator.repeatCount = ValueAnimator.INFINITE
alphaAnimator.duration = 1000
alphaAnimator.start()
val animatorSet = AnimatorSet()
animatorSet.playSequentially(
ObjectAnimator.ofFloat(modelRenderable, "translationY", 0.0f)
ObjectAnimator.ofFloat(modelRenderable, "translationY", 1.5f)
ObjectAnimator.ofFloat(modelRenderable, "translationY", 0.0f)
)
animatorSet.duration = 600
animatorSet.start()
But this is giving me an error "Could not find property setter method setTranslationY on com.google.ar.sceneform.rendering.ModelRenderable".
You should animate the node where the renderable is attached to and not the model itself. The node has a setLocalPosition(Vector3) method which you can use for the ObjectAnimator. So to animate for example the position of your model you should do something like this:
ObjectAnimator nodeAnimator = ObjectAnimator.ofObject(
yourNode,
"localPosition",
new Vector3Evaluator(),
Vector3.zero(), new Vector3(0f, 1f, 0f));
Then you can set the duration, repeat count, mode etc. You can also scale and rotate the model over a animation.
Basically I want something to fade-in and then fade-out. Initially it should be invisible, and when the animation is ended, it should also be invisible.
targetView.alpha = 0f;
var aa = AlphaAnimation(0f, 1.0f);
aa.duration=2000;
aa.repeatMode = Animation.REVERSE;
aa.repeatCount = 1;
I have tried the code above, but it did not work. it seems that the alpha animation is multiplying the initial alpha with that animation. So, 0f * 1.0f = 0f = invisible.
After searching for an answer, I have tried this, but it did not work.
aa.fillBefore= true;
aa.fillAfter=true;
How can I make the animation ignore the initial alpha value? Is it impossible, and I should change the visibility at the start/end of the animation manually?
Yes you are right about "it seems that the alpha animation is multiplying the initial alpha with that animation. So, 0f * 1.0f = 0f = invisible.".
Just don't use targetView.alpha = 0f;, use targetView.setVisibility(View.INVISIBLE); instead.
I'm doing some animation in my application. Works fine except for one small detail. The UI is unresponsive until the animation is completed. I cannot scroll, not do anything else.
I read that putting this in a Runnable is not the solution. So I'm at a lose.
Ultimately I'd like to have each object using a different duration based on the size of the object so the animation runs faster on a smaller circle and slower on a bigger circle.
Here is the code I have to test my animation :
HoleView holeView = (HoleView) view.findViewById(R.id.holeView1);
ObjectAnimator oa1 = ObjectAnimator.ofInt(holeView, "animationTime", 0, holeView.getAnimationTime());
oa1.setDuration(holeView.getAnimationTime());
holeView = (HoleView) view.findViewById(R.id.holeView2);
ObjectAnimator oa2 = ObjectAnimator.ofInt(holeView, "animationTime", 0, holeView.getAnimationTime());
oa2.setDuration(holeView.getAnimationTime());
holeView = (HoleView) view.findViewById(R.id.holeView3);
ObjectAnimator oa3 = ObjectAnimator.ofInt(holeView, "animationTime", 0, holeView.getAnimationTime());
oa3.setDuration(holeView.getAnimationTime());
holeView = (HoleView) view.findViewById(R.id.holeView4);
ObjectAnimator oa4 = ObjectAnimator.ofInt(holeView, "animationTime", 0, holeView.getAnimationTime());
oa4.setDuration(holeView.getAnimationTime());
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(oa1).with(oa2).with(oa3).with(oa4);
animatorSet.start();
Try using value animator instead:
Create a method that returns a ValueAnimator:
public static ValueAnimator animate(float from, float to, long duration) {
ValueAnimator anim = ValueAnimator.ofFloat(from, to);
anim.setDuration(duration);
return anim;
}
Then where you use it do the following if for instance you want to set the scale of an ImageView called imageView
//where 0 is the start value, 1 is the end value, and 850 is the duration in milliseconds
ValueAnimator imageAnimator = animate(0, 1, 850);
imageAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator anim) {
float scale = (Float) anim.getAnimatedValue();
imageView.setScaleX(scale);
imageView.setScaleY(scale);
}
});
imageAnimator.start();