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
Related
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.
Sorry for the vague title. I really cannot present this question is few words. OK, let me describe what I want to achieve.
First, I have a vertical LinearLayout including two views. View A is visible and as big as possible(match parent). For View BI set it VIEW.GONE be default.
Then, triggered by something, View A will be scaled into half of its size. And View B will be shown by setting VIEW.VISIBLE. In order to make View A and View B have the same height, I assigned equal weight to them in xml. The following figures illustrate what I want to implement.
figure
To scale View A, I make use of View.ScaleX(0.5f) and View.ScaleY(0.5f). Actually I implement the scale function in the animation way and set View B visibility in EndAnimation Callback.
But View A and View B cannot be shown properly. Half of each view is masked by unknown 'white block'.
And I also check the height-width of View A, before and after scaling, it does not change at all.
So is it possible to implement such feature?
Thanks so much.
You want to add a scaleY animation on View A before displaying View B right? It is absolutely possible. I'm not sure what is the 'white-block' you're referring to. You should give some of your code if you want help debugging.
Otherwise, you should use an ObjectAnimator like this one :
ObjectAnimator animator = ObjectAnimator.ofFloat(viewA, "scaleY", 1f, 0.5f);
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
viewB.setVisibility(View.VISIBLE);
// You can display the view b with an animation
ObjectAnimator.ofFloat(viewB, "alpha", 0f, 1f).start();
// Remember to reset the scale factor since there will be a re-layout
viewA.setScaleY(1.0f);
}
});
animator.start();
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;
}
}
How can I change the position of view through code? Like changing its X, Y position. Is it possible?
For anything below Honeycomb (API Level 11) you'll have to use setLayoutParams(...).
If you can limit your support to Honeycomb and up you can use the setX(...), setY(...), setLeft(...), setTop(...), etc.
Yes, you can dynamically set the position of the view in Android. Likewise, you have an ImageView in LinearLayout of your XML file. So you can set its position through LayoutParams.But make sure to take LayoutParams according to the layout taken in your XML file. There are different LayoutParams according to the layout taken.
Here is the code to set:
LayoutParams layoutParams=new LayoutParams(int width, int height);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
There are different valid answers already, but none seems to properly suggest which method(s) to use in which case, except for the corresponding API level restrictions:
If you can wait for a layout cycle and the parent view group supports MarginLayoutParams (or a subclass), set marginLeft / marginTop accordingly.
If you need to change the position immediately and persistently (e.g. for a PopupMenu anchor), additionally call layout(l, t, r, b) with the same coordinates. This preempts what the layout system will confirm later.
For immediate (temporary) changes (such as animations), use setX() / setY() instead. In cases where the parent size doesn't depend on WRAP_CHILDREN, it might be fine to use setX() / setY() exclusively.
Never use setLeft() / setRight() / setBottom() / setTop(), see below.
Background:
The mLeft / mTop / mBottom / mRight fields get filled from the corresponding LayoutParams in layout(). Layout is called implicitly and asynchronously by the Android view layout system. Thus, setting the MarginLayoutParams seems to be the safest and cleanest way to set the position permanently. However, the asynchronous layout lag might be a problem in some cases, e.g. when using a View to render a cursor, and it's supposed to be re-positioned and serve as a PopupMenu anchor at the same time. In this case, calling layout() worked fine for me.
The problems with setLeft() and setTop() are:
Calling them alone is not sufficient -- you also need to call setRight() and setBottom() to avoid stretching or shrinking the view.
The implementation of these methods looks relatively complex (= doing some work to account for the view size changes caused by each of them)
They seem to cause strange issues with input fields: EditText soft numeric keyboard sometimes does not allow digits
setX() and setY() work outside of the layout system, and the corresponding values are treated as an additional offset to the left / top / bottom / right values determined by the layout system, shifting the view accordingly. They seem to have been added for animations (where an immediate effect without going through a layout cycle is required).
There is a library called NineOldAndroids, which allows you to use the Honeycomb animation library all the way down to version one.
This means you can define left, right, translationX/Y with a slightly different interface.
Here is how it works:
ViewHelper.setTranslationX(view, 50f);
You just use the static methods from the ViewHelper class, pass the view and which ever value you want to set it to.
I would recommend using setTranslationX and setTranslationY. I'm only just getting started on this myself, but these seem to be the safest and preferred way of moving a view. I guess it depends a lot on what exactly you're trying to do, but this is working well for me for 2D animation.
You can try to use the following methods, if you're using HoneyComb Sdk(API Level 11).
view.setX(float x);
Parameter x is the visual x position of this view.
view.setY(float y);
Parameter y is the visual y position of this view.
I hope it will be helpful to you. :)
For support to all API levels you can use it like this:
ViewPropertyAnimator.animate(view).translationYBy(-yourY).translationXBy(-yourX).setDuration(0);
Set the left position of this view relative to its parent:
view.setLeft(int leftPosition);
Set the right position of this view relative to its parent:
view.setRight(int rightPosition);
Set the top position of this view relative to its parent:
view.setTop(int topPosition);
Set the bottom position of this view relative to its parent:
view.setBottom(int bottomPositon);
The above methods are used to set the position the view related to its parent.
Use LayoutParams.
If you are using a LinearLayout you have to import android.widget.LinearLayout.LayoutParams, else import the proper version of LayoutParams for the layout you're using, or it will cause a ClassCastException, then:
LayoutParams layoutParams = new LayoutParams(int width, int height);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
NB: Note that you can use also imageView.setLeft(int dim), BUT THIS WON'T set the position of the component, it will set only the position of the left border of the component, the rest will remain at the same position.
Use RelativeLayout, place your view in it, get RelativeLayout.LayoutParams object from your view and set margins as you need. Then call requestLayout() on your view. This is the only way I know.
In Kotlin you can do it as below;
view
.animate()
.x(50f)
.y(100f)
.duration = 500L
I found that #Stefan Haustein comes very close to my experience, but not sure 100%. My suggestion is:
setLeft() / setRight() / setBottom() / setTop() won't work sometimes.
If you want to set a position temporarily (e.g for doing animation, not affected a hierachy) when the view was added and shown, just use setX()/ setY() instead. (You might want search more in difference setLeft() and setX())
And note that X, Y seem to be absolute, and it was supported by AbsoluteLayout which now is deprecated. Thus, you feel X, Y is likely not supported any more. And yes, it is, but only partly. It means if your view is added, setX(), setY() will work perfectly; otherwise, when you try to add a view into view group layout (e.g FrameLayout, LinearLayout, RelativeLayout), you must set its LayoutParams with marginLeft, marginTop instead (setX(), setY() in this case won't work sometimes).
Set position of the view by marginLeft and marginTop is an unsynchronized process. So it needs a bit time to update hierarchy. If you use the view straight away after set margin for it, you might get a wrong value.
One thing to keep in mind with positioning is that each view has an index relative to its parent view. So if you have a linear layout with three subviews, the subviews will each have an index: 0, 1, 2 in the above case.
This allows you to add a view to the last position (or the end) in a parent view by doing something like this:
int childCount = parent.getChildCount();
parentView.addView(newView, childCount);
Alternatively you could replace a view using something like the following:
int childIndex = parentView.indexOfChild(childView);
childView.setVisibility(View.GONE);
parentView.addView(newView, childIndex);