I want to animate a scale an ImageView vertically, wait, then perform another scale on the same ImageView. I've simplified the code below but it's essentially what I want to do:
ScaleAnimation animate = new ScaleAnimation(1,1,1,2);
animate.setDuration(1000);
animate.fillAfter(true);
ScaleAnimation animateAgain = new ScaleAnimation(1,1,2,1);
animate.setDuration(1000);
animate.fillAfter(true);
view.startAnimation(animate);
view.startAnimation(animateAgain);
I've tried multiple ways of waiting for the first animation to finish (Thread.sleep etc.) but it doesn't seem to make any difference. I assume the animation is rendered in the onDraw method or something? I'm not entirely sure how scale animations work so I can't really grasp how to solve my problem.
What's the simplest way of doing what I want to accomplish?
Thanks guys :)
There are a couple of ways:
Attach an AnimationListener to your animations.
ScaleAnimation animate = new ScaleAnimation(1,1,1,2);
animate.setDuration(1000);
animate.fillAfter(true);
animate.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
ScaleAnimation animateAgain = new ScaleAnimation(1,1,2,1);
animateAgain.setDuration(1000);
animateAgain.fillAfter(true);
view.startAnimation(animateAgain);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationStart(Animation animation) {
}});
view.startAnimation(animate);
Alternately, you can set an animation delay for your second animation.
ScaleAnimation animateAgain = new ScaleAnimation(1,1,2,1);
animateAgain.setDuration(1000);
animateAgain.fillAfter(true);
animateAgain.setStartOffset(firstAnimationDuration + delay);
Related
I am developing a mobile game, in the game, the user presses buttons before they shrink, in order to make the game 'fun,' the buttons shrink at a faster and faster rate, so when the user presses one button, another button pops up that shrinks faster. So far I have this code which handles the user input and shrinking of the button, however, once pressed there is no animation, it instantly snaps to the 'shrunk' size, I'm not sure how to go about fixing this issue.
button.setOnClickListener(new Button.OnClickListener(){
#Override
public void onClick(View arg0) {
ViewGroup.LayoutParams params = button.getLayoutParams();
Integer value = 120;
while(value >= 2) {
value = value - 1;
SystemClock.sleep(50);
params.width = value;
params.height = value;
button.setLayoutParams(params);
};
}
});
I added the SystemClock.sleep(50) line as I thought that is why it was snapping (i.e. it was so quick that I just didn't see the animation) but that is not the case as the app just hangs until the button's size is updated.
P.s. I am quite new when it comes to developing mobile apps.
Edit
Forget what I wrote in my original post. Please try the code below and let me know whether this helps you out.
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.0f, 0.0f); //start and end value
valueAnimator.setDuration(2000); //you can replace 2000 with a variable you can change dynamically
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
button.setScaleX(animatedValue);
button.setScaleY(animatedValue);
}
});
valueAnimator.start();
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
valueAnimator.pause();
}
});
Original answer
I would follow the suggestion of 0X0nosugar.
In your Android file tree, under your res directory, add an Android Resource Directory (right-click on res folder > new) and call it "anim". Android Studio probably automatically treat as a folder which holds animation if you use that name.
Right-click again on the anim-folder > new > Animation resource file.
Name it what you want. In my example I have named it button_animator.
Your file tree will look like this:
Your button_animator.xml could look like this:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="0.1"
android:fromYScale="1.0"
android:toYScale="0.1"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="500" />
</set>
You can tailor the end scale of the button using these lines:
android:toXScale="0.1"
and
android:toYScale="0.1"
Inside your code you can dynamically tailor your animation:
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Animation shrinkButton = AnimationUtils.loadAnimation(MainActivity.this, R.anim.button_animator); //reference the animator
shrinkButton.setDuration(5000); //dynamically set the duration of your animation
button.startAnimation(shrinkButton); //start the animation. Since it is inside an onclicklistener, the animation start on a button click event
shrinkButton.setAnimationListener(new Animation.AnimationListener() { //you could use an AnimationListener to do something on certain event, like at the end of the animation
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) { //you probably want to something onAnimationEnd, otherwise the button will snap back into its original size.
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
});
In the onAnimationEnd you have to decide what you want to do at the end of the animation. Just a couple of ideas:
#Override
public void onAnimationEnd(Animation animation) { //you probably want to something onAnimationEnd, otherwise the button will snap back into its original size.
button.setScaleX(0.1f); //same size as in toScale size in the animator xml
button.setScaleY(0.1f);
}
Or, if you want the button to become invisible:
#Override
public void onAnimationEnd(Animation animation) {
button.setVisibility(View.INVISIBLE);
}
I have a ViewPager in my app, and I'd like it to shake when it is loaded for the first time. This is to show the user that there are more pages available. How can I achieve that programmatically?
However, I don't want the pager to automatically scroll to the next page.
ViewPager has a method for swiping programmatically: fakeDragBy
https://developer.android.com/reference/android/support/v4/view/ViewPager.html#fakeDragBy(float)
Here's how you can achieve this:
Call viewPager.beginFakeDrag()
Start an animator. In the AnimatorUpdateListener override onAnimationUpdate so it calls viewPager.fakeDragBy()
In the AnimatorListener override onAnimationEnd so it calls viewPager.endFakeDrag() After this call, the ViewPager should settle back to the initial page.
vpaliyX's answer using translation won't show the next page. You need to use fakeDragBy so that the user can briefly see the edge of the next page.
As requested, here is a code snippet:
private float drag;
...
#Override
protected void onResume() {
super.onResume();
mViewPager.postDelayed(new Runnable() {
#Override
public void run() {
mViewPager.beginFakeDrag();
drag = 0;
float density = getResources().getDisplayMetrics().density;
// 120 is the number of dps you want the page to move in total
ValueAnimator animator = ValueAnimator.ofFloat(0, 120 * density);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float progress = (float) valueAnimator.getAnimatedValue();
mViewPager.fakeDragBy(drag - progress);
drag = progress;
}
});
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) { }
#Override
public void onAnimationEnd(Animator animator) {
mViewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animator) { }
#Override
public void onAnimationRepeat(Animator animator) { }
});
animator.setDuration(400);
animator.start();
}
}, 300);
}
I put in a 300 ms start delay. I don't really know if that's necessary or not.
Note the expression drag - progress which gives a negative number. To get a leftward swipe, you have to use a negative value.
Also note that I didn't include any code to determine if this was the first time the app was opened.
You can play with the start delay, the animation duration, and the number of dps to swipe until you get the effect you are looking for.
If you want to shake it, you can use animation for it.
I had done the same stuff for FloatingActionButton, and it worked perfectly.
For ViewPager check out this code:
ObjectAnimator shakeAnimation=ObjectAnimator.ofFloat(yourViewPager,View.TRANSLATION_X,-25f,25f);
shakeAnimation.setDuration(100);
shakeAnimation.setRepeatCount(2);
shakeAnimation.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
yourViewPager.setTranslationY(0f);
}
});
shakeAnimation.start();
You can adjust the translation as convenient for, in my case it's just [-25,25].Also you can set a duration of animation, and the repeat count.
You can use this code for your ViewPager when the data has been loaded for the first time, however, you need to determine when the data has been loaded for the first time. But I don't think you will have problems with that.
Good luck!
My problem is most likely because of bad design, but anyway :
I have an ArrayList that holds a bunch of ImageViews (terrain blocks).
Then, I iterated through to create the ImageViews programatically, and then set their animation to an animation called moveLeft which just makes the ImageView move left for 10 seconds.
Right after the animation ends, I need to set the ImageView's visibility to GONE because it will be off-screen by then.
The problem is that I used an int count to hold the total number of ImageViews.
moveLeft.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
terrainArray.get(count - 1).setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
I'm constantly making new terrain blocks, which means my count variable is constantly changing.
However, onAnimationEnd doesn't run with the same value of count as it does when the animation starts. As a result, it ends up setting some othhe block's visibility to GONE instead.
This is because onAnimationEnd didn't actually run until 10 seconds AFTER the block was created, because the animation lasts 10 seconds.
As a result, I'm left with the wrong block number when I want to set its visibility because MORE blocks are being created AS the animation is running. (therefore increasing count's value)
Is there some sort of way I could store count's current value on the onAnimationStart method
and then pass that same value to the onAnimationEnd method? (And make it work for up to infinite numbers of terrain blocks? Also, there's only one listener, and multiple blocks can possibly end their animation at the same time.)
This is easy. If you are sure that you need the exact value of count that is at the onAnimationStart(), then create a variable in the anonymous class. Save count value in onAnimationStart() and use it in onAnimationEnd()
You can copy the following snippet.
moveLeft.setAnimationListener(new Animation.AnimationListener() {
private int mCount = 0;
#Override
public void onAnimationStart(Animation animation) {
mCount = count;
}
#Override
public void onAnimationEnd(Animation animation) {
terrainArray.get(mCount - 1).setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
Hope this helps. But, I am afraid there could be bigger design problems. Be careful boss :)
these are just the methods i can override during animationListener.
http://developer.android.com/reference/android/view/animation/Animation.AnimationListener.html
Animation animation
animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.device_box);
animation.setAnimationListener(new AnimationListener(){
#Overridepublic void onAnimationEnd(Animation arg0) {}
#Override public void onAnimationRepeat(Animation animation) {}
#Override public void onAnimationStart(Animation animation) {}});
I think adding a thread in onAnimationStart and stop it on onAnimationEnd and getting the coordinates with a loop inside the thread, this may work.
But i think that i could get some problems with buttons because, when animating them in this way, only the background moves, not button area itself
In this case, i have to investigate more with buttons
What do you think about this, is there another way to achieve what i want?
Is this the better way to do it ?
I researched, and what I got is this, not X and Y but can handle the data as get what has been animated as time.
ObjectAnimator anim = ObjectAnimator.ofFloat(box_image, "translationX", 0 , cant);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator arg0) {
double f = Math.round(arg0.getAnimatedFraction()*1000.0)/1000.0;
Log.d("datos", ""+f);
}
});
anim.setDuration(1000);
anim.start();
I want to make 2 animation on activity for 2 images, and I want to do it with 2 conditions:
1. I want to start the animation after the activity finished to load to page, so instead of putting animation code under onCreate i put it under onResume is that OK? There is a better way to do it?
2. I want the second animation will start only after the first animation is finished...
Thanks
You'll want to use an Animation.AnimationListner You can set one on your first animation that will get a callback when the animation is complete. Inside that callback you can add the code that will start the second animation.
Depending on the API level you are coding for you can use AnimationSet or AnimatorSet. Also if you are extending View or one of its subclasses you can override View.onAnimationStart() and View.onAnimationFinish(). Or use the listener Tim mentions.
public class SplashActivity extends Activity{
Animation FadeInanimation, FadeOutanimation;
ImageView img;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
img= (ImageView) findViewById(R.id.img);
//Your Code Block....
FadeInanimation = AnimationUtils.loadAnimation(this,R.anim.image_fadein);
//FadeInanimation.setRepeatCount(Animation.INFINITE);
//FadeInanimation.setRepeatMode(Animation.RESTART);
FadeInanimation.setAnimationListener(FadeInAnimationListener);
FadeOutanimation = AnimationUtils.loadAnimation(this,R.anim.image_fadeout);
//FadeOutanimation.setRepeatCount(Animation.INFINITE);
//FadeOutanimation.setRepeatMode(Animation.RESTART);
FadeOutanimation.setAnimationListener(fadeOutAnimationListener);
img.startAnimation(FadeInanimation);
}
AnimationListener FadeInAnimationListener = new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
plane.startAnimation(FadeOutanimation);
}
};
}