I wanted to animate changing height of a TextView from 0 to its real height. I'm using this code:
ValueAnimator anim = ValueAnimator.ofInt(0, height)
.setDuration(1000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
textView.getLayoutParams().height = (int)animation.getAnimatedValue();
textView.requestLayout();
}
});
anim.start();
But the animation is not that smooth (not awful, but I think it can be smoother).
What are the ways to make the smoothest animations of this sort?
The reason for its not smooth is because you are setting layout height in each update call and requesting layout too which can be cpu intensive when done several times in a second. The more smoother way i can suggest is Keeping the textview visibility as gone and then setting it to visible in code.
Then you can animate the transition using animation set. Also if you dont want to customise the animation and only want it to animate into position you can use default animation by specifying below xml attribute to the parent layout of your textview
android:animateLayoutChanges= "true"
Then when you set Visibility as visible in your code like below
textview.setVisibility(View.VISIBLE)
Your textview will automatically animate into its place.
Try the below codes. I hope it will help you.
textView.setScaleY(0);
textView.setScaleX(0);
textView.animate()
.scaleY(1)
.scaleX(1)
.setDuration(200)
.setInterpolator(INTERPOLATOR)
.start();
Declare INTERPOLATOR as a class level variable like below code.
private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
This will give you a very smooth animation. And you can play with this piece of code and can animate as you need.You can change the INTERPOLATOR.
Learn more about INTERPOLATOR.
Related
I am a beginner in android animation. I have few views inside a RelativeLayout and i wish to change view position. What are the options i have and how they differ?
I have tried following:
view.animate()
.translationX(toX)
.setDuration(duration);
and
RelativeLayout.MarginLayoutParams params = (RelativeLayout.MarginLayoutParams) view.getLayoutParams();
ValueAnimator animator = ValueAnimator.ofInt(params.rightMargin, 100);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
params5.rightMargin = (Integer) valueAnimator.getAnimatedValue();
}
});
Both changes the position of the view. Can anyone explain me the difference in these two method. What other options i have and which is the preferred option.
view.animate()
.translationX(toX)
.setDuration(duration);
I think it's preferred one, because it doesn't call measure() and layout() on each update as the second one would.
And in general:
- translationX is meant to regulate the position of a child within its parent
- perform animation through changing margin parameter isn't a good idea (it's meant to be set once and to be changed rarely if ever)
The first one is the best solution. It's specifically created for animations, so it's the most optimized version.
You have to keep in mind that if you animate a view then the whole layout will be recalculated with each movement (obviously for the translation, but not for an alpha for example), so you have to keep the layout tree as flat as possible. If it's possible try to avoid the RelativeLayouts, since they are measured twice at each frame (https://youtu.be/HXQhu6qfTVU).
You can check out a lot of cool videos here about the performance issues: https://www.youtube.com/watch?v=ORgucLTtTDI&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE
I need to scroll a TextView to the bottom till it's gone by preforming an animation like translate animation in xml. I tried to create an animation file and used translate attributes in the xml, but what it will do is that it will go down by a specific distance that I set in the xml. I don't know how to let it translate to the bottom till it's gone and disappeared from the screen. One more thing is that I also want something like a listener that will perform some action when that TextView is gone and disappeared from the screen when the animation ends.
Any suggestions will be greatly appreciated. Thanks.
You can calculate the distance you want your view to travel, but if you don't want to be exact, you can just use the screen height:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenHeight = size.y;
And then just animate the view as needed, but add a listener to do what you want at the end:
textView.animate().translationY(screenHeight).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
textView.setVisibility(View.GONE);
}
});
You still need to make the view visibility GONE at the end in the listener, just to make sure you don't see it anymore.
You can also try and use getBottom() for the distance.
I have an ImageButton with TranslateX Animation Right to Left like Merquee, So it animated right to left. Now when i click on that nothing happened. Actually click is not perform and click only perform on real position of imagebutton.
what to do any suggestion? greatly appriciate... Thanks
Use ObjectAnimator(For later version then Honeycomb) for Animatinfg Your Objects, You can use follwing code for references:
RelativeLayout layout = (RelativeLayout) findViewById(R.id.relativeLayout1);
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
ObjectAnimator mover = ObjectAnimator.ofFloat(layout, "translationX",
width, -width);
mover.setDuration(10000);
mover.setRepeatMode(Animation.INFINITE);
mover.setRepeatCount(Animation.INFINITE);
mover.start();
If you Are Using Api lower Then the HoneyComb(Like Gingerbread) then Use this Library: http://nineoldandroids.com/
It will Working As its Working in my devices.
As of now, the Animations which alter a View's matrix Only change the co-ordinates where the view is drawn, and not the actual location of View in Layout. So, its just a canvas transform when onDraw() of that View is being called.
So, you can setTranslationX(100) and view will be drawn a 100 pixels to right. But, the click area (getHitRect()) is still on the same place which was assigned to view on layout pass.
Or, you can actually place the view where it should be after animation, and run the animation in reverse.
If you want to actually alter the layout, you will have to alter that view's LayoutParams and change width/height/margin on it. Then you will have to requestLayout() on each frame of animation.
Example : This will animate left margin of a view inside a FrameLayout:
//---assuming animated view is a child of Framelayout---
FrameLayout parent;
View animatedChild;
ValueAnimator animator = new ValueAnimator();
animator.setFloatValues(0,parent.getWidth()); //--slide out to right--
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
FrameLayout.LayoutParams params = animatedChild.getLayoutParams();
Float margin = (Float) valueAnimator.getAnimatedValue();
params.leftMargin = margin.intValue();
animatedChild.requestLayout();
}
});
animator.start();
I run into similar problems before when developing my android app. And I found when animation is running, it is actually making the image layer is flowing around. So you need to click on the original position of the button, which means you may need to click on a black space if the button has started moving.
I created an Animation for the Fragments in my application. The Animation animates moving between tabs, for that purpose I need to animate the current Fragment to move completely off screen while the need Fragment slides in from the opposite side. Kind of like the Animation the ViewPager uses.
I need to specify an absolute starting and ending position of the View and since different devices have different dimensions I cannot define an Animation in XML that fits all devices. On a bigger device the View might not slide of screen completely and on a smaller device the View moves to much and continues moving when he is already off screen.
So I guess my question is: How can I define an animation in XML which will slide a Fragment off screen and at the same time fit on all devices?
My animation:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/linear_interpolator"
android:propertyName="x"
android:valueType="floatType"
android:valueTo="720"
android:valueFrom="0"
android:duration="300"/>
</set>
1) Regarding the bounty:
Existing answers on SO suggest subclassing the FrameLayout...
If you want to use an ObjectAnimator you have no choice but to subclass the View you want to animate. You need to provide the ObjectAnimator with the necessary getter and setter methods to do its magic as it essentially just calls those getter and setter methods to perform the Animation.
The question you are linking to (Animate the transition between fragments) is subclassing FrameLayout to add a setXFraction() and a getXFraction() method. They are implemented in a way to set the x value relative to the width of the FrameLayout. Only by doing this can the ObjectAnimator do anything else besides animating between absolute values.
So to summarise, the ObjectAnimator itself doesn't actually do much animating, it just calls getter and setter methods through reflection.
Is there really no way to get the actual screen pixel dimensions (not
just dp) into the xml file?
With an ObjectAnimator there is no way to achieve that. ObjectAnimators just interpolate from a start value to and end value. As I explained above, the setter method defines what actually happens.
For example, calling a custom function that returns the width would be
fine, or defining a constant that code can set, having code set said
constant to equal the screen width, then accessing that constant from
the xml would be equally useful.
You cannot insert any value from code into any xml resource. Everything contained in your xml files is compiled into your APK when you build it and cannot be changed at runtime. And that is also an answer to your other question: There is no resource or constant or anything which would be accessible in xml which contains the current screen size. Dynamic values like the screen size of the device the app is installed on cannot be a part of those resources since everything in them is compiled into your app when you build it.
2) Possible Solution: View Animations
One possible solution is to use view animations instead of an ObjectAnimator. With view animations you can specify fractions instead of just absolute values:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="-100%" android:toYDelta="0%" android:duration="1000"/>
</set>
This would animate a View from -100% to 0% of the screen height. In other words from completely of the screen to the position of the View. You can use it like this:
Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_down);
view.startAnimation(animation);
I realise that this might not be of any help to you. There are cases in which one has to use ObjectAnimators and cannot use view animations. But as long as you are able to use view animations this should pretty much solve your problem.
Link to
documentation
Related question
3) Best practice
What we really should be addressing here is what I think is a misconception on your part. As you already noticed every animation you define in xml has absolute start and end values. Those values are static and cannot be changed after compiling your app.
If you look at how the resources work with the many available selectors and also with dp, sp... then you will realise that it is designed to do one thing:
You can for example define an animation that moves a View by 100dp. Dp is a measurement of physical size, 100dp will be the exact same physical size one any screen with any pixel density. Through selectors you could alter this animation for devices with smaller or bigger screens where the animation may be moving the View too much or too little. But you can only fill in static values. Aside from working with selectors you cannot customise the animation for each device.
So you see, the resources are really just designed for anything that's static and unchanging. It works great for dimensions or string translations but sometimes with animations it can be a bit of a pain. As I said above only view animations provide a way around the static nature by providing the option of specifying fractions instead of absolute values. But in general you cannot define anything dynamic in xml.
To futher corroborate this argument look at Google's excellent DevBytes videos about animation:
View Animations
Custom Activity Animations
Cardflip Animation
Property Animations
ListView Animations
Then you will notice that not a single animation in those examples is ever defined in xml. Sure one could argue that they don't define them in xml because they want to have an easier time explaining and showing the code, but in my opinion this once again proves one point:
You cannot define an animation in static resources which depends on a purely dynamic value
Since the screen width/height will be different from device to device you need different animations for each device. Only view animations provide a way around that since they allow you to define fractions instead of absolute values. In any other case you are going to need to define the animations programmatically. Fortunately that's not difficult with ObjectAnimators:
Animator animator = ObjectAnimator.ofFloat(view, View.X, startValue, endValue);
animator.start();
This would animate the x position of the View from the start to the end value in pixels. You can pass in the width of the View to animate it like you want to:
Animator animator = ObjectAnimator.ofFloat(view, View.X, -view.getWidth(), 0.0f);
animator.start();
This would animate the View to slide in from the left. It starts completely off screen and stops in its final position. You could also just do this:
view.animate().x(targetValue);
This would animate the View from its current x position to the target x value.
But please don't misunderstand me, you should still try to define as much as possible - be it animations or anything else - in the resources. Hardcoding animations or any other value should be avoided as much as possible, unless it's necessary like in your case.
4) Summary
So to summarise:
You cannot do what you want to do with ObjectAnimators without adding the getter and setter methods you need to the View you want to animate.
If possible use view animations. With them you can define animations based on fractions of the width or height of the View.
If the above doesn't work or is not applicable to your situation then define the animations programmatically, this will work in any case.
I hope I could help you and if you have any further questions feel free to ask!
just create different folders based on screen densities.
so lets say your xml for animation is in a folder called anim (in res parent folder).
then create anim-ldpi , anim-mdpi , etc in res as well. Put your respective animation in each of these folders which represent screen density and android will select the right one.
Somebody was asking for my working solution. Here it is. It is C# code via Mono. Hopefully Java authors can figure out what it should be in that language.
public class MyFragment: Fragment {
public int TransitDuration {
get {
return 500;
}
}
public override Animator OnCreateAnimator(FragmentTransit transit, bool enter, int nextAnim) {
switch (transit) {
case FragmentTransit.FragmentOpen:
{
if (enter) {
return this.EnterFromLeftAnimator;
} else {
return this.ExitToRightAnimator;
}
}
case FragmentTransit.FragmentClose:
{
if (enter) {
return this.EnterFromRightAnimator;
} else {
return this.ExitToLeftAnimator;
}
}
default:
Animator r = base.OnCreateAnimator(transit, enter, nextAnim);
if (r == null) {
if (!this.IsAdded) {
this.OnContextHidden();
}
}
return r;
}
}
public Animator EnterFromLeftAnimator {
get {
float width = Screen.MainScreen.PixelSize.Width; // this is an object of mine; other code cached the width there long ago. It would actually be better to use the window width.
ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", width, 0);
animator.SetDuration(this.TransitDuration);
Animator r = animator as Animator;
return r;
}
}
public Animator ExitToRightAnimator {
get {
float width = Screen.MainScreen.PixelSize.Width;
ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", 0, -width);
animator.SetDuration(this.TransitDuration);
Animator r = animator as Animator;
r.AddListener(new AnimatorEndListenerAdapter(() => this.OnContextHidden()));
return r;
}
}
public Animator EnterFromRightAnimator {
get {
float width = this.ScreenWidth;
ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", -width, 0);
animator.SetDuration(this.TransitDuration);
Animator r = animator as Animator;
return r;
}
}
public Animator ExitToLeftAnimator {
get {
float width = this.ScreenWidth;
ObjectAnimator animator = ObjectAnimator.OfFloat(this, "X", 0, width);
animator.SetDuration(this.TransitDuration);
Animator r = animator as Animator;
r.AddListener(new AnimatorEndListenerAdapter(() => this.OnContextHidden()));
return r;
}
}
public override void OnActivityCreated(Bundle savedInstanceState) {
base.OnActivityCreated(savedInstanceState);
this.RetainInstance = true;
}
}
I'm trying to fade out an ImageView using an animation. The animation runs, fading out the ImageView, but then the image goes back to fully opaque. Why does this happen?
private void fadeOutImage() {
final ImageView imageView = (ImageView) activity.findViewById(R.id.imageView);
imageView.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.fadeout));
}
I tried setting the alpha to zero as below, but then the image never showed up period. Why not?
private void fadeOutImage() {
final ImageView imageView = (ImageView) activity.findViewById(R.id.imageView);
imageView.setAlpha(0);
imageView.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.fadeout));
}
To get this to work, I had to create an AnimationListener, overload the onAnimationEnd, and set the alpha to be zero there. Why?
final ImageView imageView = (ImageView) activity.findViewById(id);
Animation animation = AnimationUtils.loadAnimation(activity, R.anim.fadeout);
animation.setAnimationListener(new AnimationListener() {
#Override public void onAnimationStart(Animation animation) {
imageView.setAlpha(255);
}
#Override public void onAnimationRepeat(Animation animation) { }
#Override public void onAnimationEnd(Animation animation) {
imageView.setAlpha(0);
}
});
imageView.startAnimation(animation);
Animation XML:
<alpha
android:duration="1500"
android:fromAlpha="1.0"
android:interpolator="#android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
When you start an animation, it literally takes whatever the view currently looks like, animates it according to your supplied animation, but doesn't ever actually change the state of the view.
This can get confusing, but in your first case, after the animation ends, the ImageView just goes back to whatever it looked like before the animation - in your case, the normal opaque.
In your second example, you are setting the alpha to zero before the animation starts, and so when the animation begins playing, the image is already invisible, and so it animates this invisible ImageView - which you can't see.
Therefore, the required sequence is to start the animation before changing the ImageView at all, let it play out, and then the moment the animation ends, change the state of the ImageView so that it disappears before it gets redrawn in it's original state. This is what overriding onAnimationEnd() does.
So, to recap, in the proper implementation, there are two things that affect how the ImageView gets drawn:
The animation, which affects how the ImageView looks only while the animation is playing.
The imageView.setAlpha(0); method call, to keep the image invisible once the animation finishes.
Note: to possibly increase performance, I'd suggest using
imageView.setVisibility(View.INVISIBLE);
instead of imageView.setAlpha(0);
I'm trying to fade out an ImageView using an animation. The animation runs, fading out the ImageView, but then the image goes
back to fully opaque. Why does this happen?
The animation's effects are temporary. Once the animation has completed, the view returns to its state before the animation began.
I tried setting the alpha to zero as below, but then the image never
showed up period. Why not?
Though apparently undocumented, the fromAlpha and toAlpha parameters appear to act in a multiplicative fashion. 100% of zero is still zero. I verified this by starting the alpha to a very low value, and I could see that the animation caused the alpha to start at the low value and fade to transparent.
To get this to work, I had to create an AnimationListener, overload
the onAnimationEnd, and set the alpha to be zero there. Why?
Since the animation's effects are temporary, you need to set the object to be transparent after the animation. You also have to make sure you reset the object to be opaque before you start the animation.
It looks like you can use an ObjectAnimator starting in API 11 to set the alpha (or any other property's) values directly using rather than multiplicatively.