I'm implementing a slot machine in Android. The reels are ScrollViews with LinearLayouts as children, and the individual tiles as ImageViews vertically oriented inside the LinearLayout. I'm using an ObjectAnimator to scroll to the end of the ScrollView, using ValueAnimator.RESTART and ValueAnimator.INFINITE. The reel scrolling is nice and smooth, however when it comes time to repeat, it pauses for a second, which ruins the effect. Here's the code I'm using:
public void spinReel(SlotReel reel) {
final int reelSize = this.context.getPixelsInDPs(64);
final SlotMachineView me = this;
final SlotReel myReel = reel;
ImageView[] tiles = reel.getTiles();
int delta = tiles.length - reel.currentTileNumber();
ObjectAnimator animator = ObjectAnimator.ofInt(reel, "scrollY", tiles.length * reelSize );
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(delta * 75);
animator.setStartDelay(0);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationEnd(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
myReel.scrollTo(0, reelSize * 3);
}
});
animator.start();
}
Note that the jerkiness occurs whether or not I put the scrollTo in the Repeat callback.
Example video here:
You should try:
setRepeatMode(ValueAnimator.INFINITE);
look here
Android ValueAnimator pauses during repeat
Related
The following code is working when the supplied view is a Button but when it's a LinearLayout nothing happens (Background color doesn't change).
If I manually set the color on the LinearLayout without the ValueAnimator it works. I also tried using a ObjectAnimator but the result looked terrible.
An example call:
playBlinkAnimation(view, activity.getResources().getColor(R.color.transparent), activity.getResources().getColor(R.color.colorRed), 5000);
view.getBackground().setColorFilter(activity.getResources().getColor(R.color.colorRed), PorterDuff.Mode.DARKEN);
private void playBlinkAnimation(final View view, int colorFrom, int colorTo, int duration) {
ValueAnimator colorAnimation = new ValueAnimator();
colorAnimation.setIntValues(colorFrom, colorTo);
colorAnimation.setEvaluator(new ArgbEvaluator());
colorAnimation.setDuration(duration);
colorAnimation.setRepeatCount(ValueAnimator.INFINITE);
colorAnimation.setRepeatMode(ValueAnimator.REVERSE);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
view.getBackground().setColorFilter((int) animator.getAnimatedValue(), PorterDuff.Mode.DARKEN);
}
});
colorAnimation.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
view.getBackground().setColorFilter(null);
}
#Override
public void onAnimationCancel(Animator animation) {
view.getBackground().setColorFilter(null);
view.setAlpha(1);
view.getBackground().setColorFilter(null);
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
colorAnimation.start();
}
You can try using AnimatorSet and it's playTogether function. It takes an array of Animators, and it's designed to run multiple animations together, but it should work with only one animation as well.
Hope that helps you!
Since you seem to want your layout to transition from transparent background to opaque red, you should animate the layout's alpha parameter
ObjectAnimator animator = ObjectAnimator.ofInt(view, "alpha", 0, 1);
animator.setDuration(5000);
animator.setInterpolator(new LinearInterpolator());
animator.start();
I have a fragment with 3 LinearLayout inside. By default, the first one have the weight attribute to 1.
Each LinearLayout have a OnClickListener that trigger 2 animations:
Animation with a weight from 0 to 1 for the one that is clicked.
Animation with a weight from 1 to 0 for the one currently opened.
My problem is that sometime the animations are not finishing completely.
Here is the code triggered by the OnClickListener:
private void launchAnimation(final LinearLayout llToExpand) {
ViewWeightAnimationWrapper animationWrapper = new ViewWeightAnimationWrapper(llToExpand);
ObjectAnimator anim = ObjectAnimator.ofFloat(animationWrapper,
"weight",
animationWrapper.getWeight(),
1f);
anim.setDuration(1000);
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
Log.d("Anim1", "onAnimationEnd: Anim1 have ended");
}
#Override
public void onAnimationCancel(Animator animation) {
Log.d("Anim1", "onAnimationCancel: Anim1 have been canceled.");
}
});
ViewWeightAnimationWrapper animationWrapper2 = new ViewWeightAnimationWrapper(mLlCurrentlyOpened);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(animationWrapper2,
"weight",
animationWrapper2.getWeight(),
0f);
anim2.setDuration(1000);
anim2.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
Log.d("Anim2", "onAnimationEnd: Anim2 have ended");
mLlCurrentlyOpened = llToExpand;
}
#Override
public void onAnimationCancel(Animator animation) {
Log.d("Anim2", "onAnimationCancel: Anim2 have been canceled.");
}
});
anim.start();
anim2.start();
}
Here is the code of my ViewWeightAnimationWrapper:
public class ViewWeightAnimationWrapper {
private View view;
public ViewWeightAnimationWrapper(View view) {
if (view.getLayoutParams() instanceof LinearLayout.LayoutParams) {
this.view = view;
} else {
throw new IllegalArgumentException("The view should be a LinearLayout");
}
}
public void setWeight(float weight) {
Log.i(String.valueOf(view.getId()), "setWeight: " + weight);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
params.weight = weight;
view.requestLayout();
}
public float getWeight() {
return ((LinearLayout.LayoutParams) view.getLayoutParams()).weight;
}
Here are the log that I have when the animation isn't going to the end:
I/2131755212: setWeight: 0.5
I/2131755207: setWeight: 0.5
I/2131755212: setWeight: 0.5251222
I/2131755207: setWeight: 0.47487777
I/2131755212: setWeight: 0.55174345
I/2131755207: setWeight: 0.44825655
D/Anim1: onAnimationCancel: Anim1 have been canceled.
D/Anim1: onAnimationEnd: Anim1 have ended
D/Anim2: onAnimationCancel: Anim2 have been canceled.
D/Anim2: onAnimationEnd: Anim2 have ended
I have no clue about why my animation are not canceled every time.
How can I know what is canceling my animations? Is it possible to force the animations to finish when they are cancelled?
I haven't found the reason about why the animation is canceled.
On the documentation, they say that the animation is canceled when there the same animation is triggered on the same object, which wasn't my case.
However, i have found a workaround here that is giving me the expected result.
I have replaced the ObjectAnimator by a custom animation:
/**
* Launch the expand animation on the LinearLayout in parameter and launch the folding animation
* on the currently opened LinearLayout.
* #param llToExpand LinearLayout that we want to expand.
*/
private void launchAnimation(final LinearLayout llToExpand) {
// Create the and assign the animations
ExpandAnimation expand = new ExpandAnimation(llToExpand, 0, 1);
expand.setDuration(500);
ExpandAnimation fold = new ExpandAnimation(mLlCurrentlyOpened, 1, 0);
fold.setDuration(500);
// Start the animations
llToExpand.startAnimation(expand);
mLlCurrentlyOpened.startAnimation(fold);
// Switch the currently opened LinearLayout for the next time
mLlCurrentlyOpened = llToExpand;
}
Here is my ExpandAnimation:
public class ExpandAnimation extends Animation {
private float mStartWeight;
private final float mEndWeight;
private final LinearLayout mContent;
public ExpandAnimation(LinearLayout content, float startWeight,
float endWeight) {
mStartWeight = startWeight;
mEndWeight = endWeight;
mContent = content;
}
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContent
.getLayoutParams();
mStartWeight = lp.weight;
lp.weight = (mStartWeight + ((mEndWeight - mStartWeight) * interpolatedTime));
mContent.setLayoutParams(lp);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
I just ran into what might be the same issue. Looking at the ObjectAnimator source, it's holding a WeakReference to the animation target. In my case I was using a dedicated object created only to perform the animation, and it turned out that the weak reference was the only reference. Thus it would sometimes be de-referenced during the animation. ObjectAnimator handles this "cleanly", simply canceling the animation.
So I have a wifi signal strength scale looking like this:
Whenever the signal gets better or worse I set the y-coordinate of the scale like this:
View dBmLine = scale.findViewById(Math.abs(dBm));
float dBmLineMiddle = dBmLine.getY() + dBmLine.getHeight() / 2;
float newY = arrowMiddle - dBmLineMiddle;
scale.setY(newY);
This looks very clumsy and I want to add a smooth animation for the scale's oldY to newY. Understanding very little about animations, I tried the following:
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, scale.getY(),
Animation.ABSOLUTE, newY);
scale.startAnimation(translateAnimation);
But the scale didn't move, it only flickers.
scale.animate().setDuration(50).translationY(newY);
50 is millis, 1000 = 1 second.
Also, if you want to add a listener on that animation you could use :
scale.animate().setDuration(50).translationY(300).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
}
#Override
public void onAnimationEnd(Animator animator) {
}
#Override
public void onAnimationCancel(Animator animator) {
}
#Override
public void onAnimationRepeat(Animator animator) {
}
});
I want to implement zoom feature on an ImageButton by Property Animation. For example, when I click the button, it will zoom out. And when I click it again, it will zoom in.
Here is part of my code:
OnClickListener clickPlayButtonHandler = new OnClickListener() {
#Override
public void onClick(View v) {
final ImageButton clickedButton = (ImageButton) v;
if((Boolean) v.getTag()) {
// zoom out
clickedButton.animate().setInterpolator(new AnticipateInterpolator()).setDuration(500).scaleXBy(-0.4f).scaleYBy(-0.4f).setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
clickedButton.setImageResource(R.drawable.bg_pause);
System.out.println(clickedButton.getWidth()); // output the width of the button for checking
}
#Override
public void onAnimationEnd(Animator animation) {
clickedButton.setTag(false);
int d = clickedButton.getWidth();
System.out.println(clickedButton.getWidth());// output the width of the button for checking
}
#Override
public void onAnimationCancel(Animator animation) {}
#Override
public void onAnimationRepeat(Animator animation) { }
});
} else {
// process zoom in
}
}
};
I printed the width of the button before the animation start and the animation finished. However, I found they were the same. I thought when the zoom out animation finished the button width should be small than before. But it didn't.
Could not change view size by ViewPropertyAnimator?
There won't be changes in clickedButton.getWidth(), because the width of a view is not affected by scaling. You can think of getWidth() as a way to get unscaled width of a view. To change the width of a View requires a new measure/layout pass.
ViewPropertyAnimator doesn't change View's width/height or anything that could potentially trigger another layout pass. This is simply because layout passes are expensive and therefore could cause frame skipping, which is the last thing we want see during an Animation.
If you need to get scaled width of the button, you can do getScaleX() * clickedButton.getWidth()
Try the ObjectAnimator:
ObjectAnimator xAnimator =
ObjectAnimator.ofFloat(clickedButton, "scaleX", 1.0f, -0.4f);
ObjectAnimator yAnimator =
ObjectAnimator.ofFloat(clickedButton, "scaleY", 1.0f, -0.4f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.playTogether(xAnimator, yAnimator);
animatorSet.setInterpolator(new AnticipateInterpolator());
animatorSet.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
clickedButton.setImageResource(R.drawable.bg_pause);
System.out.println(clickedButton.getWidth());
}
#Override
public void onAnimationEnd(Animator animation) {
clickedButton.setTag(false);
int d = clickedButton.getWidth();
System.out.println(clickedButton.getWidth());
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animatorSet.start();
Hi I am applying multiple animations to a view using AnimatorSet and ValueAnimator.
When the user touches the view, successive animations are applied to the view. I do this to have a "zooming effect" when the user is moving his fingers on the view.
The view is a custom gallery.
Here is the code for animation:
private void createAnim(final View v) {
animating = true;
if (D)
Log.i(TAG, "sarting animation");
try {
v.clearAnimation();
v.getAnimation().reset();
} catch (Exception e) {
}
set = new AnimatorSet();
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
if (D)
Log.i(TAG, "Animation ended");
animating = false;
}
#Override
public void onAnimationCancel(Animator animation) {
animating = false;
if (D)
Log.i(TAG, "Animation cancelled");
}
});
set.setDuration(100);
ValueAnimator v1 = ObjectAnimator.ofFloat(v, "scaleX", mScaleFactor);
ValueAnimator v2 = ObjectAnimator.ofFloat(v, "scaleY", mScaleFactor);
animationList.add(v1);
animationList.add(v2);
set.playTogether(v1, v2);
set.start()
}
I have an onTouchListener and on action move I successively call this method increasing or decreasing the mScaleFactor.
When the user releases the finger I would like the view to go back to previous state, I mean to erase all animations applied as if they were never applied to view. The problem is you can easily do that for one animation but for many like that I just can't find the right way. Here is what I have tried:
-Adding the animations to an ArrayList and doing animation.reverse() on each one of them with a delay of +100 added to each of them so the execute one after another but the end result just doesn't seem exactly the same as before animations.
v.clearAnimation(); only cancels the last animation
v.getAnimation().reset(); same only works for last/active animation.
Is there a method to just get the view back as it was before any animation has been started?
Thanks a lot
Reversing the effects of an AnimatorSet can be tricky simply because there is no "reverse" method. The easiest method in this case, would probably simply be another AnimatorSet that does the opposite of the original. The issue here would be is your AnimatorListener could be out of sync. If you do this way and you want to keep the boolean, then you'd have to implement it as somewhat of a semaphore.
public static class AnimatorTracker implements AnimatorListener{
int counter;
public AnimatorTracker() {
counter = 0;
}
public boolean isAnimating() {
return counter == 0;
}
#Override
public void onAnimationStart(Animator animation) {
counter++;
}
#Override
public void onAnimationRepeat(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
counter--;
}
#Override
public void onAnimationCancel(Animator animation) {
// Canceling an animation invokes onAnimationEnd, so nothing needs to be done.
}
}
Then just make the sets the same way you just did:
AnimatorTracker tracker = new AnimatorTracker(); // or make it a global reference
AnimatorSet set = new AnimatorSet();
AnimatorSet reverseSet = new AnimatorSet();
ValueAnimator v1 = ObjectAnimator.ofFloat(v, "scaleX", mScaleFactor);
ValueAnimator v2 = ObjectAnimator.ofFloat(v, "scaleY", mScaleFactor);
ValueAnimator reverseV1 = ObjectAnimator.ofFloat(v, "scaleX", 1.0f);
ValueAnimator reverseV2 = ObjectAnimator.ofFloat(v, "scaleY", 1.0f);
set.addListener(tracker);
set.setDuration(100);
set.playTogether(v1, v2);
reverseSet.addListener(tracker);
reverseSet.setDuration(100);
reverseSet.playTogether(reverseV1, reverseV2);
Call set.start() when you want to animate forward. Call reverseSet.start() when you want to animate back.