45

It appears that the old view animations (translate, scale, and so on) are no longer accepted by the AnimationInflater, at least as of ICS. I read its code in 4.0.4, and it explicitly expects only the XML elements set, objectAnimator, animator.

Even though the documentation at http://developer.android.com/guide/topics/resources/animation-resource.html continues to include the view animations, they appear to be deprecated. Trying to use them results in, for instance, the error java.lang.RuntimeException: Unknown animator name: translate.

As such, it becomes necessary to use Android's ObjectAnimator. However, it does not accept fractional values of the associated dimension of itself or its parent (width for translationX, for example) as the old view animations did in the form "75%p".

Constructing the ObjectAnimator manually at runtime, by programmatically fetching the size of the Fragment, isn't feasible because the FragmentTransaction only accepts declarative animations specified by a resid.

My goal is to translate offscreen a Fragment that is filling up an entire Activity (I'm basically doing a shift transition between two fragments). This is the existing TranslationAnimation implementation (slide_in_right.xml, which along with its counterpart slide_out_left.xml is for some reason not exposed in android.R.anim, and I therefore have to duplicate them in my codebase):

<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
    android:fromXDelta="100%p"
    android:toXDelta="0"
    android:duration="@android:integer/config_mediumAnimTime"/>
</set>

My API level is set to 14.

Thanks!

brockoli
  • 4,516
  • 7
  • 38
  • 45
orospakr
  • 590
  • 1
  • 4
  • 7
  • complete working example code here [enter link description here][1] [1]: http://stackoverflow.com/questions/25699178/cant-get-objectanimator-working-with-xfraction/27924975#27924975 – mathheadinclouds Jan 13 '15 at 15:05
  • Actually, there is `AnimatorInflater` and `AnimationUtils`, and both are still supported (API level 22). You are still able to use the 'old' tween animations if you like, but they won't always work, true. One example is if you use native (i.e. not support) fragments - they require the new animators and then you need to write some code. There are solutions for this, like subclasses with 'fractionX/Y' properties, but I think it is better to use a ValueAnimator with a custom listener to update the positions - you don't need a subclass then. – wujek Jun 27 '15 at 22:13

5 Answers5

51

Actually object animators accept fractional values. But maybe you didn't understand the underlying concept of an objectAnimator or more generally a value animator. A value animator will animate a value related to a property (such as a color, a position on screen (X,Y), an alpha parameter or whatever you want). To create such a property (in your case xFraction and yFraction) you need to build your own getters and setters associated to this property name. Lets say you want to translate a FrameLayout from 0% to 25% of the size of your whole screen. Then you need to build a custom View that wraps the FrameLayout objects and write your getters and setters.

public class SlidingFrameLayout extends FrameLayout
{
    private static final String TAG = SlidingFrameLayout.class.getName();

    public SlidingFrameLayout(Context context) {
        super(context);
    }

    public SlidingFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public float getXFraction()
    {
        int width = getWindowManager().getDefaultDisplay().getWidth();
        return (width == 0) ? 0 : getX() / (float) width;
    }

    public void setXFraction(float xFraction) {
        int width = getWindowManager().getDefaultDisplay().getWidth();
        setX((width > 0) ? (xFraction * width) : 0);
    }
}

Then You can use the xml way to declare the object animator putting xFraction under the property xml attribute and inflate it with a AnimatorInflater

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator 
xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="xFraction" 
android:valueType="floatType"
android:valueFrom="0"
android:valueTo="0.25" 
android:duration="500"/>

or you can just use the java line code

ObjectAnimator oa = ObjectAnimator.ofFloat(menuFragmentContainer, "xFraction", 0, 0.25f);

Hope it helps you!

Olivier,

Olivier Marty
  • 572
  • 5
  • 6
  • 60
    Now the million dollar question: Why weren't these properties added to Android when they added new properties in 3.0? This seems like a major oversight to me. – Christopher Perry Feb 28 '13 at 00:44
  • Just to add for the case of setting CustomAnimation on fragmentTransaction for 3.0+, you need to extend the parent Layout of your Fragment's view. For instance, if your fragment's root Layout is a RelativeLayout, you'll require to extend RelativeLayout and add getter setter for xFraction or any other property you want. Then it will apply to the fragment. – Achin Kumar May 20 '14 at 19:33
  • 1
    who is calling getXFraction and setXFraction here? – BamsBamx Sep 05 '14 at 21:44
  • I don't see why this should be the answer to the question above. I was not able to solve the exact same problem with this answer. – stoefln Dec 25 '14 at 19:04
  • 5
    Instead of a subclass with the frationX/Y properties, one could use the `ValueAnimator` and install an `AnimatorUpdateListener`, whose `onAnimationUpdate()` method would perform the necessary calculations and set the properties of the things being animated. I think it is better than creating a view/layout subclass. – wujek Jun 27 '15 at 22:09
  • when you call `setX(x)` method, it sets X position of the view offset by x from the left edge of the screen, how do you retrieve the original position of the view before the animation so that it would use it to translate until the original x-position is reached? – Buddy Aug 16 '15 at 13:12
  • 1
    I found the correct method to make relative translation, setTranslationX() – Buddy Aug 16 '15 at 13:19
9

Android SDK implementation of FragmentTransaction expects an Animator while, for some obscure reason, the support library implementation expects an Animation, if you use Fragment implementation from support library your translate animation will work

sherpya
  • 4,890
  • 2
  • 34
  • 50
  • Yes, you can't choice. If you want to use the support library must use the tween animation otherwise must use the property animation!! – Davide Oct 10 '14 at 09:32
  • The reason is probably that API 4 does not support Animator without nineoldandroids – A. Steenbergen Dec 14 '14 at 23:45
8

Here is an important gotcha for those who are trying to create view animations like translate and scale.

Property animations are located in a directory named "animator": res/animator/filename.xml

BUT, view animations must be put in a directory simply called "anim": res/anim/filename.xml

I first put my view animation in an "animator" folder, and was confused about Android Studio complaining about how translate was not a valid element. So, NO, view animations are not deprecated. They just have their own location for some confusing reason.

Johannes
  • 1,370
  • 16
  • 15
4

here is complete working example (works as outlined by author of accepted answer.)

Pressing the button toggles between 2 fragments A and B (by slide animation right to left). The fragments are just stupid text (AAAAAA and BBBBB) with different backgrounds.

MainActivity.java

package com.example.slidetrans;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;


public class MainActivity extends Activity {

    private static final String TAG = "Main";

    boolean showingA = true;
    Button button;

    A a;
    B b;

    private void incarnate(FragmentManager fm){
        int layoutId = R.id.frame;
        boolean fragmentWasNull = false;
        Fragment f = fm.findFragmentById(layoutId);
        if (f == null){
            Log.i(TAG, "fragment is null");
            if (showingA){
                f = a = new A();
            } else {
                f = b = new B();
            }
            fragmentWasNull = true;
        } else {
            Log.i(TAG, "fragment is not null");
            showingA = (f instanceof A);
            updateButtonText();
        }
        if (fragmentWasNull){
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(layoutId, showingA ? a : b,  "main").commit(); 
        }
    }
    private void updateButtonText(){
        button.setText(showingA ? "slide in B" : "slide in A");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        FragmentManager fm = getFragmentManager();
        button = (Button)findViewById(R.id.button);
        incarnate(fm);
        OnClickListener listener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fm = getFragmentManager();
                FragmentTransaction transaction = fm.beginTransaction();
                transaction.setCustomAnimations(R.anim.in, R.anim.out);
                transaction.replace(R.id.frame, showingA ? new B() : new A()).commit();
                showingA = !showingA;
                updateButtonText();
            }
        };
        button.setOnClickListener(listener);
    }
}

LL.java

package com.example.slidetrans;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;

public class LL extends LinearLayout {

    public LL(Context context) {
        super(context);
    }

    public LL(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public float getXFraction() {
        final int width = getWidth();
        if (width != 0) return getX() / getWidth();
        else return getX();
    }

    public void setXFraction(float xFraction) {
        final int width = getWidth();
        float newWidth = (width > 0) ? (xFraction * width) : -9999;
        setX(newWidth);
    }
}

main.xml (layout)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
>
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="slide in B" />

    <FrameLayout
        android:id="@+id/frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

    </FrameLayout>

</LinearLayout>

a.xml (layout)

<?xml version="1.0" encoding="utf-8"?>
<com.example.slidetrans.LL xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#00FF00"
>

    <TextView
        android:id="@+id/aText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="AAAAAAAAAAAAAAAAAA"
        android:textSize="30sp"
        android:textStyle="bold"
    />

</com.example.slidetrans.LL>

b.xml (layout)

<?xml version="1.0" encoding="utf-8"?>
<com.example.slidetrans.LL xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#FFFF00"
>

    <TextView
        android:id="@+id/bText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:textStyle="bold"
        android:text="BBBBBBBBBB"
    />

</com.example.slidetrans.LL>

in.xml (anim)

<?xml version="1.0" encoding="utf-8"?>
<set  xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
    android:duration="500"
    android:interpolator="@android:anim/linear_interpolator"
    android:propertyName="xFraction"
    android:valueFrom="1.0"
    android:valueTo="0.0"
    android:valueType="floatType" />

</set>

out.xml (anim)

<?xml version="1.0" encoding="utf-8"?>
<set  xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
    android:duration="500"
    android:interpolator="@android:anim/linear_interpolator"
    android:propertyName="xFraction"
    android:valueFrom="0.0"
    android:valueTo="-1.0"
    android:valueType="floatType" />

</set>
mathheadinclouds
  • 3,507
  • 2
  • 27
  • 37
0

View animations are not deprecated. If you are using Eclipse you can find them under tween animation as a resource type when creating a new Android XML file.