How to define a screen-size specific animation in XML - android

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;
}
}

Related

Is there a way to parametrise an animation?

I've created a simple animation to make a view move up and down repeatedly:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:fromYDelta="0"
android:toYDelta="???"
android:duration="1000"
android:repeatCount="1000000"
android:repeatMode="reverse"/>
</set>
I'm using an animation resource for that like it is suggested in the docs. However, at compile time I don't know how far down the view is supposed to go (toYDelta) as it will depend on the device screen.
Is there a way to parametrise that somehow and if not, what would be a way around it?
You can implement the same animation programmatically like that in Kotlin:
val bounceAnimation = TranslateAnimation(0f, 0f, fromYDelta, toYDelta).apply {
duration = 1000
repeatCount = 1000000
repeatMode = Animation.REVERSE
interpolator = AccelerateDecelerateInterpolator()
}
view.startAnimation(bounceAnimation)
You can express those Values as Percentage, eg. : "20%p" is 20% of the parent
See for more info
https://developer.android.com/guide/topics/resources/animation-resource
Documentation says here:
A vertical and/or horizontal motion. Supports the following attributes in any of the following three formats: values from -100 to 100 ending with "%", indicating a percentage relative to itself; values from -100 to 100 ending in "%p", indicating a percentage relative to its parent; a float value with no suffix, indicating an absolute value. Represents a TranslateAnimation.
%p will help you do what you want.

Android how to slide view from left to right

I have an ImageView I would like to make it slide with an animation from its original position to the new one.
Currently my view is on the left close to the screen border. I need to move it to the opposite side and have it stayed there permanently.
Is there a way to accomplish this ?
For now I've tried Transition Animation like this one :
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate android:fromXDelta="0%" android:toXDelta="500%"
android:fromYDelta="0%" android:toYDelta="0%" android:fillAfter="true"/>
</set>
But the problem is that the androud:toXDelta value is arbitrary and I don't know how to calculate it.
With the current value, the view seems to come from outside the screen and goes a little bit further than its original position but at the end of the animation immediately comes back to its original position. That is not what I am looking for.
Thanks for the help !
To get the ImageView to stay at its final location at the end of the animation, put the following attributes inside your <set> tag:
android:fillAfter="true"
android:fillEnabled="true"
You can also do this type of thing pretty easily programmatically using a TranslateAnimation. You would do something similar to this:
ImageView viewToSlide = (ImageView) findViewById(R.id.your_image_view);
TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 0.80f,
Animation.RELATIVE_TO_PARENT, 0,
Animation.RELATIVE_TO_PARENT, 0);
animation.setDuration(5000);
animation.setFillEnabled(true);
animation.setFillAfter(true);
viewToSlide.startAnimation(animation);
EDIT IN RESPONSE TO COMMENTS
To answer your first question below:
So you had originally mentioned that xDelta was an "arbitrary" value that was difficult to calculate, and you are right! So the way we can position our view relative to the width of its parent is to use the Animation.RELATIVE_TO_PARENT identifier, which identifies our next dimension value as a value that should be multiplied by the dimension of the parent itself. So in the constructor of our TranslateAnimation, we have -- Animation.RELATIVE_TO_PARENT, 0.80f -- which just means "take the width of the parent, and multiply it by 0.80." That gives us 80% of the width of the parent. The other values with zeroes in them just give us values of zero, because it will be "the width/height of the parent multiplied by zero."
To answer your second question:
The reason the animation accelerates is because I believe the default interpolator (the animation modifier that defines the speeds at which the values of the animation play out) is one of the accelerator interpolators. You can change this using the setInterpolator() method, and pass in any of the values found here: http://developer.android.com/reference/android/R.interpolator.html

Animating Margin by ValueAnimator vs ViewPropertyAnimator translationX

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

Android scale view and re-layout

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();

Android animate view comming into screen that pushes the current full screen view out

I need this for a project pre API11.
Im looking for a way to add a view in from right, that will cause the current full screen view to move out to left as to allow the new view to be visibe in the screen.
the new view is about 1/3 of the screen width so thats the distance the current fusll screen view should move out of the screen.
i have tired various solutions with TranslateAnimation(), although im happy with the animation, i'm having issues with all the clickable areas in the original view not being where they are supposed to be.
so i started playing with the layout to position it correctly with negative margin, but after a couple of seconds when a new layout request comes the view is popped back into place. also this solution had a choppy effect when moving the view on the animation end listener.
what would be the most simple solution that has good animation when the new view is entering or exiting and that puts the view in the correct place in terms of layout.
Since you're using API11, use the Animator API. All views have properties now with corresponding setters and getters. To shift a view to the left out of scope, get the width of the screen then do:
View viewToShiftOut = getOutView();
ObjectAnimator outAnim = ObjectAnimator.ofFloat(viewToShiftOut, "x", 0, -widthOfScreen);
outAnim.setDuration(1000);
outAnim.start();
This will shift the view and it's contents entirely out of the screen for a duration of 1 second (1000 miliseconds). To shift one in it's place do this:
View viewToShiftIn = getInView();
ObjectAnimator inAnim = ObjectAnimator.ofFloat(viewToShiftIn, "x", widthOfScreen, 0);
inAnim.setDuration(1000);
inAnim.start();
All the properties, clickable areas, etc. will follow the view so you'll be done when the animations finish. You can animate them together like so:
View viewToShiftOut = getOutView();
View viewToShiftIn = getInView();
ObjectAnimator outAnim = ObjectAnimator.ofFloat(viewToShiftOut, "x", 0, -widthOfScreen);
ObjectAnimator inAnim = ObjectAnimator.ofFloat(viewToShiftOut, "x", widthOfScreen, 0);
outAnim.setDuration(1000);
inAnim.setDuration(1000);
outAnim.start();
inAnim.start();
Blog post
EDIT: I noticed you wanted to only shift the view by a 1/3rd, so you can get the amount of pixels to shift each by widthOfScreen/3.
EDIT 2: Another advantage is you can use it with hardware acceleration without trouble which most 3.0+ tablets have.
Instead of using fillAfter and fillEnabled, as you're probably doing, you will need to programmatically alter your layout in the onAnimationEnd of an AnimationListener to match your desired final positioning.
To remove the flicker, call clearAnimation() on the View you are animating, also in onAnimationEnd.

Categories

Resources