Android ObjectAnimator freezes UI - android

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();

Related

How do I make a smooth animation with ValueAnimator?

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()

Animation of a TextView's text size and not the entire TextView

Is there a way to animate only the TextView's text size without scaling the entire TextView's layout?
Am trying to achieve a similar effect, Note the text re-sizes to a single line while its size becomes smaller.
This could be achieved with a ValueAnimator and from the top of my head I think it should look something like this:
final TextView tv = new TextView(getApplicationContext());
final float startSize = 42; // Size in pixels
final float endSize = 12;
long animationDuration = 600; // Animation duration in ms
ValueAnimator animator = ValueAnimator.ofFloat(startSize, endSize);
animator.setDuration(animationDuration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
tv.setTextSize(animatedValue);
}
});
animator.start();
As a follow up on #korrekorre answer: The documentation suggests to use the simpler ObjectAnimator API
final TextView tv = new TextView(getApplicationContext());
final float endSize = 12;
final int animationDuration = 600; // Animation duration in ms
ValueAnimator animator = ObjectAnimator.ofFloat(tv, "textSize", endSize);
animator.setDuration(animationDuration);
animator.start();
There is just one caveat: The property you pass to the constructor ("textSize" in this case) must have a public setter method for this to work.
You can also pass a startSize to the constructor, if you don't then the interpolator will use the current size as starting point
With Kotlin, create an extension function like this:
fun TextView.sizeScaleAnimation(endSize: Float, durationInMilliSec: Long) {
val animator = ObjectAnimator.ofFloat(this, "textSize", endSize)
animator.duration = durationInMilliSec
animator.start()
}
Use it like this:
val endSize = resources.getDimension(R.dimen.my_new_text_size)
myTextView.sizeScaleAnimation(endSize, 200L)

In Android how to use ObjectAnimator to move to point x along a curve

I have an image view "stone" and am moving it from its current position to a X,Y position. I want it to move along a curve. Please let me know how I can do that(i have set the min api as 11)
ObjectAnimator moveX = ObjectAnimator.ofFloat(stone, "x", catPos[0] );
ObjectAnimator moveY = ObjectAnimator.ofFloat(stone, "y", catPos[1] );
AnimatorSet as = new AnimatorSet();
as.playTogether(moveX, moveY);
as.start();
The answer by Budius seems perfectly useful to me.
Here are the animator objects I use:
Purpose: Move View "view" along Path "path"
Android v21+:
// Animates view changing x, y along path co-ordinates
ValueAnimator pathAnimator = ObjectAnimator.ofFloat(view, "x", "y", path)
Android v11+:
// Animates a float value from 0 to 1
ValueAnimator pathAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
// This listener onAnimationUpdate will be called during every step in the animation
// Gets called every millisecond in my observation
pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
float[] point = new float[2];
#Override
public void onAnimationUpdate(ValueAnimator animation) {
// Gets the animated float fraction
float val = animation.getAnimatedFraction();
// Gets the point at the fractional path length
PathMeasure pathMeasure = new PathMeasure(path, true);
pathMeasure.getPosTan(pathMeasure.getLength() * val, point, null);
// Sets view location to the above point
view.setX(point[0]);
view.setY(point[1]);
}
});
Similar to: Android, move bitmap along a path?
you have two options:
both needs a Path object that defines your curve:
Path path = new Path();
path. // define your curve here
if using Lollipop only (API 21) use ObjectAnimator.ofFloat(...path) like this:
ObjectAnimator.ofFloat(stone, View.X, View.Y, path).start();
if that's not an options, you use an AnimatorListener to receive updates about the each animator frame and use the PathMeasure to get the values on that point, like this:
PathMeasure pm;
float point[] = {0f, 0f};
private final ValueAnimator.AnimatorUpdateListener listener =
new ValueAnimator.AnimatorUpdateListener(){
#Override
public void onAnimationUpdate (ValueAnimator animation) {
float val = animation.getAnimatedFraction();
pm.getPosTan(pm.getLength() * val, point, null);
stone.setTranslationX(point[0]);
stone.setTranslationY(point[1]);
}
}
// and then to animate
pm = new PathMeasure(path, false);
ValueAnimator a = ValueAnimator.ofFloat(0.0f 1.0f);
a.setDuration(/* your duration */);
a.setInterpolator(/* your interpolator */);
a.addUpdateListener(listener);
a.start();
Play around with the interpolators. For example set 2 different Interpolators for x and y :
moveX.setInterpolator(new AccelerateDecelerateInterpolator());
moveY.setInterpolator(new DecelerateInterpolator());
Theres more (LinearInterpolator, AccelerateInterpolator...) but I think this should be the combination you want.
Thanks for the answers, but I found my own answer.Just added some extra x,y positions in the Objectanimator stmt. It went to those positions before coming to the final one and traced a path!
ObjectAnimator moveX = ObjectAnimator.ofFloat(stone, "x", catPos[0]-20,catPos[0]-10,catPos[0] );
ObjectAnimator moveY = ObjectAnimator.ofFloat(stone, "y",catPos[1]-20,catPos[1]-10, catPos[1] );
The Path class has methods for creating non-straight lines of several types; arcs, circles, ovals, rectangles, cubic & quadratic bezier curves, which you can then animate your object along.
As Budius points out, you do need to code for API 21 (Lollipop) or later to use paths with object translation. It is now over two years old.
On the other hand, there does seem a dearth of anything-more-than-very-simple examples of using paths "in the wild", so perhaps there's a reason they've not caught on yet.
Check this link for 3 ways of animating several properties of a View in parallel:
https://developer.android.com/guide/topics/graphics/prop-animation#view-prop-animator
The 1st way of doing it is using AnimatorSet.
Below is the 2nd way:
public static ObjectAnimator ofPropertyValuesHolder (Object target,
PropertyValuesHolder... values)
The API doc describes this constructor for ObjectAnimator as:
"This variant should be used when animating several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows you to associate a set of animation values with a property name."
Example:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder transX = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
PropertyValuesHolder transY = PropertyValuesHolder.ofKeyframe("translationY", kf0, kf1, kf2);
ObjectAnimator transAnim = ObjectAnimator.ofPropertyValuesHolder(view2animate, transX, transY);
transAnim.setDuration(5000);
transAnim.start();
The example above moves view2animate in both x and y axis at the same time.
The 3rd way is to use ViewPropertyAnimator.

Is it possible to get real time coordinates of an ImageView while it is in Translate animation?

I have an image of a bullet in an ImageView that does Translate animation.
I need to show real time coordinates to show how far it is from target in real time.
ImageView myimage = (ImageView)findViewById(R.id.myimage);
Animation animation = new TranslateAnimation(100, 200, 300, 400);
animation.setDuration(1000);
myimage.startAnimation(animation);
animation.setRepeatCount(Animation.INFINITE);
Is it possible to get real time x and y coordinates of the image while it is doing TranslateAnimation ?
And if its not possible using TranslateAnimation, is there any other way that gives real time coordinates of image while in motion ?
I tried -
int x = myimage.getLeft();
int y = myimage.getTop();
and
int[] firstPosition = new int[2];
myimage.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
myimage.getLocationOnScreen(firstPosition);
int x = firstPosition[0];
int y = firstPosition[1];
but in both the ways, its giving the initial static coordinate of the ImageView.
Here's a complete example based on what user3249477 and Vikram said:
final TextView positionTextView = (TextView)findViewById(R.id.positionTextView);
ImageView myimage = (ImageView)findViewById(R.id.imageView);
ObjectAnimator translateXAnimation= ObjectAnimator.ofFloat(myimage, "translationX", 0f, 100f);
ObjectAnimator translateYAnimation= ObjectAnimator.ofFloat(myimage, "translationY", 0f, 100f);
translateXAnimation.setRepeatCount(ValueAnimator.INFINITE);
translateYAnimation.setRepeatCount(ValueAnimator.INFINITE);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(translateXAnimation, translateYAnimation);
set.start();
translateXAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
imageXPosition = (Float)animation.getAnimatedValue();
}
});
translateYAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
imageYPosition = (Float)animation.getAnimatedValue();
String position = String.format("X:%d Y:%d", (int)imageXPosition, (int)imageYPosition);
positionTextView.setText(position);
}
});
You can use an ObjectAnimator (API 11+):
ImageView iv = (ImageView)findViewById(R.id.markerRed);
// Create animators for x and y axes
ObjectAnimator oax = ObjectAnimator.ofFloat(iv, "translationX", 0f, 100f);
ObjectAnimator oay = ObjectAnimator.ofFloat(iv, "translationY", 0f, 100f);
oax.setRepeatCount(Animation.INFINITE);
oay.setRepeatCount(Animation.INFINITE);
// Combine Animators and start them together
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(oax, oay);
set.start();
Then fetch the animation values like this:
Log.e("TAG", "X: " + oax.getAnimatedValue() + " Y:" + oay.getAnimatedValue());
If you add these values to the initial ImageView's coordinates, you'll get the current location.
I had this idea: you can extend the standard TranslateAnimation and intercept every step of the animation by overriding the applyTransformation method.
Take a look at this incomplete/untested snippet:
private class ObservableTranslateAnimation extends TranslateAnimation{
private float matrixValues[] = new float[9];
private float actualDx;
private float actualDy;
public ObservableTranslateAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
}
// ... more constructors
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
//After that super is called, the matrix gets updated!
//get the current matrix and extract what you need
t.getMatrix().getValues(matrixValues);
actualDx = matrixValues[Matrix.MTRANS_X];
actualDy = matrixValues[Matrix.MTRANS_Y];
/*
Notify someone here (a listener?), or just read the values of actualDx, actualDy from outside
You can also play around with the other Matrix values.
*/
}
}

Apply one animation to multiple views at the same time

So Id like to rotate a handful of views all at the same time, all using the same rotation specs. The issue is that for some reason the rotation acts differently for the second element. Apparently this has to do with the animation object actually changing state in between those two lines of code. Obviously I could just create a seperate Animation object and apply it, but I feel like there is an easier way (I have about 15 views)
Rotates only the first view correctly:
Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotationtoportrait);
target.startAnimation(rotateAnim);
lightBtn.startAnimation(rotateAnim);
Rotates both correctly
Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotationtoportrait);
Animation rotateAnim2 = AnimationUtils.loadAnimation(this, R.anim.rotationtoportrait);
target.startAnimation(rotateAnim);
lightBtn.startAnimation(rotateAnim2);
XML:
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="-90"
android:toDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500" android:fillAfter="true">
Anyone have any ideas?
Do it like this:
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "y", 100f);
arrayListObjectAnimators.add(anim);
ObjectAnimator anim1 = ObjectAnimator.ofFloat(view, "x", 0f);
arrayListObjectAnimators.add(anim1);
ObjectAnimator[] objectAnimators = arrayListObjectAnimators.toArray(new ObjectAnimator[arrayListObjectAnimators.size()]);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(objectAnimators);
animSetXY.duration(1000);
animSetXY.start();
So I guess this just isn't possible, so I created a helper method to just apply the same animation to a list of views:
public void doRotations(ArrayList<View> views, int start, int end, int xprop, float xscale, int yprop, float yscale, int duration, Boolean fillAfter){
for(int i = 0; i < views.size(); i++){
RotateAnimation temp = new RotateAnimation(start, end, xprop, xscale, yprop, yscale);
temp.setDuration(duration);
temp.setFillAfter(fillAfter);
views.get(i).startAnimation(temp);
}
}
Definitely a hack, but I guess thats all I'm able to do right now
I was able to do this in Kotlin by programmatically creating one AnimatorSet.
1. Create an ArrayList of the views you want to animate
var viewList = arrayListOf(view1,view2,view3)
2. Loop through the ArrayList and create a growing AnimatorSet
var ix = 0
var anim = AnimatorSet()
var viewList = arrayListOf(view1,view2,view3)
viewList.forEach{
// Initiate the animator set with one ObjectAnimator
if(ix == 0){
anim = AnimatorSet().apply {
play(ObjectAnimator.ofFloat(it, "rotation", 0F, 360F))
}
}
// Add one ObjectAnimator at a time to the growing AnimatorSet
else{
var currentAnim = ObjectAnimator.ofFloat(it,"rotation",0F,360F)
anim = AnimatorSet().apply {
play(anim).with(currentAnim)
}
}
ix++
}
3. Start the animation
button.setOnClickListener {
AnimatorSet().apply {
play(anim)
start()
}

Categories

Resources