Android:使用ObjectAnimator将视图沿着视图尺寸的分数值进行平移

45

看起来旧的视图动画(如translatescale等)不再被AnimationInflater接受,至少在ICS中是这样。我在4.0.4中阅读了其代码,它明确只期望XML元素setobjectAnimatoranimator

尽管http://developer.android.com/guide/topics/resources/animation-resource.html的文档仍包括视图动画,但它们似乎已被弃用。尝试使用它们会导致错误,例如java.lang.RuntimeException: Unknown animator name: translate

因此,使用Android的ObjectAnimator变得必要。然而,它不像旧的视图动画那样以"75%p"这样的形式接受其本身或其父项的关联维度的分数值(例如对于translationX的宽度)。

在运行时编程手动构建ObjectAnimator,通过以编程方式获取Fragment的大小,是不可行的,因为FragmentTransaction只接受由resid指定的声明性动画。

我的目标是将填充整个Activity的Fragment平移出屏幕(我基本上在两个Fragment之间进行了移位转换)。这是现有的TranslationAnimation实现(slide_in_right.xml及其对应的slide_out_left.xml由于某种原因未在android.R.anim中公开,因此我不得不在我的代码库中复制它们):

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

我的 API 等级设置为14。

谢谢!


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - mathheadinclouds
实际上,AnimatorInflaterAnimationUtils两者都仍然受支持(API级别为22)。如果您愿意,仍然可以使用“旧”的补间动画,但并不总是有效。其中一个例子是,如果您使用原生(即非支持)片段,则需要新的动画器,然后您需要编写一些代码。对于此类问题有解决方案,例如带有“fractionX/Y”属性的子类,但我认为最好使用ValueAnimator与自定义监听器来更新位置 - 然后您就不需要子类了。 - wujek
5个回答

51

实际上,对象动画器接受小数值。但是也许您没有理解对象动画器或更一般的值动画器的基本概念。值动画器将动画化与属性相关的值(例如颜色、屏幕上的位置(X、Y)、alpha参数或任何您想要的)。要创建此类属性(在您的情况下为 xFraction 和 yFraction),您需要构建自己的关联到此属性名称的getter和setter。假设您希望将一个 FrameLayout 从整个屏幕大小的0%平移至25%,则需要构建包装 FrameLayout 对象的自定义视图并编写您的getter和setter。

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

然后您可以使用XML方式声明对象动画,将 xFraction 放在属性 XML 属性下,并使用 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"/>

或者你可以直接使用Java代码行。

ObjectAnimator oa = ObjectAnimator.ofFloat(menuFragmentContainer, "xFraction", 0, 0.25f);
希望能对你有所帮助! Olivier,

60
现在是一个价值百万美元的问题:为什么在Android 3.0中添加新属性时,没有将这些属性添加进去?在我看来,这似乎是一个重大的疏忽。 - Christopher Perry
仅针对在3.0+版本中为fragmentTransaction设置CustomAnimation的情况,您需要扩展Fragment视图的父布局。例如,如果您的片段的根布局是RelativeLayout,则需要扩展RelativeLayout并添加getter setter以获取xFraction或任何其他属性。然后它将应用于该片段。 - Achin Kumar
1
这里是谁在调用getXFraction和setXFraction方法? - BamsBamx
5
可以使用ValueAnimator并安装一个AnimatorUpdateListener,而不是使用具有fractionX/Y属性的子类。onAnimationUpdate()方法将执行必要的计算并设置正在动画化的内容的属性。我认为这比创建视图/布局子类更好。 - wujek
1
我找到了正确的方法来进行相对平移,即setTranslationX()。 - Buddy
显示剩余2条评论

9

Android SDK实现的FragmentTransaction期望使用Animator,而支持库实现由于某种不明原因期望使用Animation。如果您使用支持库实现的Fragment,则可以正常使用翻译动画。


是的,你不能选择。如果想要使用支持库,必须使用补间动画;否则必须使用属性动画! - Davide
可能的原因是API 4不支持Animator,需要使用nineoldandroids。 - A. Steenbergen

8
这里有一个重要的注意事项,对于那些试图创建视图动画(如translatescale)的人来说。
属性动画位于名为“animator”的目录中:res/animator/filename.xml 但是,视图动画必须放置在一个简单地称为“anim”的目录中:res/anim/filename.xml 我最初将我的视图动画放在一个“animator”文件夹中,并因Android Studio抱怨translate不是有效元素而感到困惑。因此,,视图动画并没有被弃用。出于某种令人困惑的原因,它们只有自己的位置。

4
这里是完整的工作示例(按照被接受答案的作者说明运行)。点击按钮可以在两个片段A和B之间切换(通过从右向左的幻灯片动画)。这些片段只是带有不同背景的愚蠢文本(AAAAAA和BBBBB)。
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);
    }
}

主要.xml (布局)

<?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(布局)

<?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(动画)

<?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>

0

视图动画并未被弃用。如果您正在使用Eclipse,可以在创建新的Android XML文件时将它们作为资源类型找到Tween Animation下。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接