I have a few buttons in an Android app of mine, "Start", "Stop", and "Pause". When Start is pressed is becomes disabled and Stop and Pause become enabled. Similarly, when Stop or Pause is pressed, Start becomes enabled again. When these buttons change state, I would like to animate the change with a bouncing animation to visually indicate to the user that the button is now available. The problem is that the animation is actually changing the buttons size(background). Check out this gif to see what I mean (watch the Stop button):
Here is the code for the animation:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/linear_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="1.1"
android:fromYScale="1.0"
android:toYScale="1.1"
android:pivotX="50%"
android:pivotY="50%"
android:duration="200"
android:repeatCount="0"
android:repeatMode="reverse" />
</set>
And how I'm applying it to the buttons:
Animation scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.scale_button);
startButton.startAnimation(scaleAnimation);
stopButton.startAnimation(scaleAnimation);
Why is the Stop button size changing and how can I keep it from doing this?
Thanks!
Note: I originally set the "repeatCount" of the animation to 1 so that it would scale the button back down. I changed it to 0 to debug this problem but the same thing is still happening.
I'd refrain from using Animation. You have to play with fillEnabled, fillAfter, fillBefore flags, it's not convenient and non-intuitive API to use.
Instead, prefer Animators API.
The animation you'd like to implement will look something like this:
val scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, 1.0f, 1.1f).setDuration(200)
val scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, 1.0f, 1.1f).setDuration(200)
val downscaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, 1.1f, 1.0f).setDuration(200)
val downscaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, 1.1f, 1.0f).setDuration(200)
val set = AnimatorSet()
set.playTogether(scaleX, scaleY)
set.play(downscaleX).after(scaleX)
set.playTogether(downscaleX, downscaleY)
set.start()
Or, you can play with fluent API:
view.animate()
.scaleXBy(0.1f)
.scaleYBy(0.1f)
.setDuration(200)
.withEndAction {
view.animate()
.scaleXBy(-0.1f)
.scaleYBy(-0.1f)
.duration = 200
}
You can achieve this simply by
v.animate().scaleX(1.1f).scaleY(1.1f).setDuration(200).withEndAction(new Runnable() {
#Override
public void run() {
v.animate().scaleX(1.0f).scaleY(1.0f).setDuration(200);
}
});
where "v" is your view.
Regards
Related
I am trying to make a fade out animation to a View, but I don't want the standard one. I want the fade out to start from left to right (or right to left no matter). Any idea on how to do it? I didn't really found anything for android on SO. This is my code for the moment:
val fadeOut = AlphaAnimation(1f, 0f)
fadeOut.interpolator = AccelerateInterpolator()
fadeOut.duration = 3000L
fadeOut.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
}
override fun onAnimationEnd(animation: Animation) {
mTextView.visibility = View.VISIBLE
}
override fun onAnimationRepeat(animation: Animation) {}
})
mTextView.startAnimation(fadeOut)
From what I can understand, it sounds like you want a reveal animation rather than a fade in/out animation. Your best bet would be to write your own reveal Animation, but if you're ready to settle for a simpler hack, then you could use the circular reveal animation that's available in ViewAnimationUtils. Just start the circular far off to the left/right so that the circular reveal feels like a linear reveal.
final View myView = findViewById(R.id.animatedImageView);
int cx = myView.getMeasuredWidth() * 2;
int cy = myView.getMeasuredHeight() / 2;
int finalRadius = (int) Math.hypot(myView.getWidth()*2, myView.getHeight());
Animator anim;
// for reveal
anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
// for hiding
// anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, finalRadius, 0);
// Do note that you'll need to factor in the 'reveal' time taken in by the extra empty space.
// You could probably get around this by creating your own interpolator,
// but in which case, you might as well just create your own reveal animation.
anim.setDuration(3000);
// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
Try this:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<alpha
android:duration="1000"
android:fromAlpha="0.0"
android:interpolator="#android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
<translate
android:fromXDelta="100%p" android:toXDelta="0"
android:duration="500" />
</set>
I have a simple animation:
<rotate android:fromDegrees="0"
android:toDegrees="180"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="4"
android:repeatMode="reverse"
android:duration="1000"
android:interpolator="#android:anim/linear_interpolator" />
<translate
android:duration="2000"
android:fromXDelta="10%p"
android:toXDelta="90%p"
android:repeatCount="1"
android:repeatMode="reverse"
android:fillAfter="true"/>
and I run it on an ImageView. I have an onclick event setup on the ImageView:
imgCorrect1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "explode ");
}
});
Here is how I start the animation:
Animation anim = AnimationUtils.loadAnimation(this, R.anim.rottrans);
imgCorrect1.startAnimation(Anim1);
The problem is that while the animation is going, the onclick doesn't get called while the animation is running if I tap on the image while it's animating.
I have tried to search on this issue, but the posts are all to animate an ImageView -when- the onclick is called, not the opposite.
I hope I have been clear here in the description. I have tried to provide all of the relevant code to explain the issue.
You are using Animation API, which animates the matrix of the view. Thus you see a ghost of the original view being animated, whereas the view still is positioned in the original place.
Instead, use Animator API. Here's how it may look like:
ImageView imageView = ...;
ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, View.ROTATION, 0, 180);
rotate.setRepeatCount(4);
rotate.setRepeatMode(ValueAnimator.REVERSE);
rotate.setDuration(1000);
rotate.setInterpolator(new LinearInterpolator());
ObjectAnimator translate = ObjectAnimator.ofFloat(imageView, View.TRANSLATION_X, 0, 100);
translate.setRepeatCount(1);
translate.setRepeatMode(ValueAnimator.REVERSE);
translate.setDuration(2000);
AnimatorSet set = new AnimatorSet();
// Play either sequentially or together
set.playSequentially(rotate, translate);
// set.playTogether(rotate, translate);
set.start();
How can I create one animation which always translates from left to right and then if animation stops it turns the other way and translates from right to left. I can create Translate animation in xml and programatically too.
Create this xml, it will translate from left to right and from right to left.
/res/anim/translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillEnabled="true"
android:fillAfter="true" >
<translate
android:interpolator="#android:anim/linear_interpolator"
android:fromXDelta="0%p"
android:toXDelta="10%p"
android:duration="2000"
android:startOffset="0" />
<translate
android:interpolator="#android:anim/linear_interpolator"
android:fromXDelta="10%p"
android:toXDelta="-10%p"
android:duration="2000"
android:startOffset="2000" />
</set>
Suppose you want to apply this animation to an image then write the code below in your class file.
ImageView image = (ImageView)findViewById(R.id.imageView1);
Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.translate);
image.startAnimation(animation);
Edit:
If you want your animation to repeat infinitely add the following attributes to the translate tag.
android:repeatCount="infinite"
android:repeatMode="restart"
Depends on your min API level actually. Essentially You should look at ViewPropertyAnimator class.
Let's say you have view to animate, and parent - parent of this view. Your code would look like this:
final float startX = 0; //start position
final float endX = parent.getWidth() - view.getWidth(); //end position - right edge of the parent
API 12+:
view.animate().translationX(endX).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
view.animate().translationX(startX).start();
}
}).start();
API 16+:
view.animate().translationX(endX).withEndAction(new Runnable() {
#Override
public void run() {
view.animate().translationX(startX).start();
}
}).start();
Your startX and endX might be different - depending on your needs.
Please note that start() method call is optional.
Also I have not mentioned solution for API <12 since I personally think that nobody should support those legacy APIs :)
I want to use a ValueAnimator to make a TextView's text color blink twice between two different colors but I want to create the Animation in XML. I cannot find any examples. Any help will be appreciated.
Update
The code below works perfect. The color changes from black to blue, blue to black, black to blue, and blue to black with 500ms in between each reverse repeat. I'm however trying to get this to work from an animator xml file.
ValueAnimator colorAnim = ObjectAnimator.OfInt(objectToFlash, "textColor", (int)fromColor, (int)toColor);
colorAnim.SetDuration(500);
colorAnim.SetEvaluator(new ArgbEvaluator());
colorAnim.RepeatCount = 3;
colorAnim.RepeatMode = ValueAnimatorRepeatMode.Reverse;
xml
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="textColor"
android:duration="500"
android:valueFrom="#color/black"
android:valueTo="#color/ei_blue"
android:repeatCount="3"
android:repeatMode="reverse" />
Code
ValueAnimator anim = (ObjectAnimator)AnimatorInflater.LoadAnimator(Activity, Resource.Animator.blinking_text);
anim.SetTarget(objectToFlash);
Using xml causes the color of the TextView's text color to change as many times as it can within 500ms.
Update
I think what I need are Keyframes to mimic in xml what the OfInt call is doing programmatically. Trying this now but no luck so far.
Try this:
private static final int RED = 0xffFF8080;
private static final int BLUE = 0xff8080FF;
ValueAnimator colorAnim = ObjectAnimator.ofInt(myTextView, "backgroundColor", RED, BLUE);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();
Or try this untested method via xml: *res/animator/property_animator.xml*
<set >
<objectAnimator
android:propertyName="backgroundColor"
android:duration="3000"
android:valueFrom="#FFFF8080"
android:valueTo="#FF8080FF"
android:repeatCount="-1"
android:repeatMode="reverse" />
</set>
now in Java code:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myTextView);
set.start();
Starting from API LEVEL > 21 the same effect can be made with static method ObjectAnimator.ofArgb like this:
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void animateText(TextView text) {
ObjectAnimator animator = ObjectAnimator.ofArgb(text, "textColor", Color.WHITE, Color.RED);
animator.setDuration(500);
animator.setRepeatCount(3);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.start();
}
The problem that you describe is that the object animator specified in XML is not correctly assigning the ArgbEvaluator for color interpolation.
To resolve the issue, create the object animator XML to tween between the colors as you desire. Then, in the source code, do the following ensuring that the evaluator used by the animator is an ArgbEvaluator:
ObjectAnimator colorAnimator = (ObjectAnimator)AnimatorInflater.loadAnimator(this, R.animator.color_rotation);
colorAnimator.setTarget(objectToFlash);
colorAnimator.setEvaluator(new ArgbEvaluator());
colorAnimator.start();
Here is simple xml android animation:
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="-110"
android:toYDelta="-110" android:duration="1000" android:fillAfter="true" />
I want to move animated object from the center of the screen to 0, 0 positions. How cat I make it (it should work at all screen resolutions)
My Answer:
Thank you guys for your help. But I have fix my problem by other way, dynamically, without xml. Here's full code of this method:
public void replace(int xTo, int yTo, float xScale, float yScale) {
// create set of animations
replaceAnimation = new AnimationSet(false);
// animations should be applied on the finish line
replaceAnimation.setFillAfter(true);
// create scale animation
ScaleAnimation scale = new ScaleAnimation(1.0f, xScale, 1.0f, yScale);
scale.setDuration(1000);
// create translation animation
TranslateAnimation trans = new TranslateAnimation(0, 0,
TranslateAnimation.ABSOLUTE, xTo - getLeft(), 0, 0,
TranslateAnimation.ABSOLUTE, yTo - getTop());
trans.setDuration(1000);
// add new animations to the set
replaceAnimation.addAnimation(scale);
replaceAnimation.addAnimation(trans);
// start our animation
startAnimation(replaceAnimation);
}
My solution:
Thank you guys for your help. But I have fix my problem by other way, dynamically, without xml. Here's full code of this method:
public void replace(int xTo, int yTo, float xScale, float yScale) {
// create set of animations
replaceAnimation = new AnimationSet(false);
// animations should be applied on the finish line
replaceAnimation.setFillAfter(true);
// create scale animation
ScaleAnimation scale = new ScaleAnimation(1.0f, xScale, 1.0f, yScale);
scale.setDuration(1000);
// create translation animation
TranslateAnimation trans = new TranslateAnimation(0, 0,
TranslateAnimation.ABSOLUTE, xTo - getLeft(), 0, 0,
TranslateAnimation.ABSOLUTE, yTo - getTop());
trans.setDuration(1000);
// add new animations to the set
replaceAnimation.addAnimation(scale);
replaceAnimation.addAnimation(trans);
// start our animation
startAnimation(replaceAnimation);
}
First, insert object in container that occupies entire screen. Then modify you animation xml like this
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="50%p" android:fromYDelta="50%p"
android:toXDelta="0%p" android:toYDelta="0%p"
android:duration="1000"
android:fillAfter="true" />
You can also check this android API reference http://developer.android.com/guide/topics/resources/animation-resource.html#translate-element
%p suffix translates element "in percentage relative to the parent size".
If you want it to work on all screens you aren't going to be able to hardcode x,y values. Animations allow you to use percents. This animation would move your view from 0% x and y(relative to itself. In other words where ever it is before the animation it will start from there) It will move to 0%p x and y (which means 0% relative to the parent) This should take you to the top left. Depending on how far into the corner you want it you may try 5%p or 10%p to get some extra margin.
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0%" android:fromYDelta="0%" android:toXDelta="0%p"
android:toYDelta="0%p" android:duration="1000" android:fillAfter="true" />
In my case I used an ImageView, randomly moved it using setY() and setX() and then animated it on the new position with a Scale animation in xml.
That didn't work. It always started from the initial position and scaled out to the new position.
The way I solved it was to wrap the animated View in a RelativeLayout and then move the RelativeLayout with the ImageView inside it.
PivotX and PivotY always uses the center of the parent view.