10

我在我的应用程序中创建Animation了一个。Fragments动画在选项卡Animation之间移动,为此我需要对当前进行动画处理Fragment以完全移出屏幕,同时需要Fragment从另一侧滑入。有点像Animation用途ViewPager

我需要指定的绝对开始和结束位置,View并且由于不同的设备具有不同的尺寸,我无法Animation在 XML 中定义适合所有设备的。在较大的设备View上,屏幕可能不会完全滑动,而在较小的设备上,View当他已经离开屏幕时,会移动很多并继续移动。

所以我想我的问题是:如何在 XML 中定义一个动画,它会滑出Fragment屏幕,同时适合所有设备?

我的动画:

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

3 回答 3

21

1)关于赏金:

SO上的现有答案建议将FrameLayout子类化......

如果你想使用 anObjectAnimator你别无选择,只能将View你想要动画的子类化。您需要提供ObjectAnimator必要的 getter 和 setter 方法来发挥它的魔力,因为它本质上只是调用这些 getter 和 setter 方法来执行Animation.

您链接到的问题(动画片段之间的过渡)是子类化FrameLayout以添加 asetXFraction()和 agetXFraction()方法。它们的实现方式是将 x 值设置为相对于FrameLayout. 只有这样做才能ObjectAnimator在绝对值之间进行动画处理之外的其他任何事情。

总而言之,它ObjectAnimator本身实际上并没有做太多动画,它只是通过反射调用 getter 和 setter 方法。

真的没有办法将实际的屏幕像素尺寸(不仅仅是 dp)放入 xml 文件吗?

没有ObjectAnimator办法做到这一点。ObjectAnimators只需从起始值到结束值进行插值。正如我上面解释的,setter 方法定义了实际发生的情况。

例如,调用一个返回宽度的自定义函数就可以了,或者定义一个代码可以设置的常量,让代码将所述常量设置为等于屏幕宽度,然后从 xml 访问该常量同样有用。

您不能将代码中的任何值插入到任何 xml 资源中。xml 文件中包含的所有内容都会在您构建时编译到您的 APK 中,并且在运行时无法更改。这也是对您其他问题的回答:在包含当前屏幕大小的 xml 中没有可访问的资源或常量或任何内容。安装应用程序的设备的屏幕大小等动态值不能成为这些资源的一部分,因为它们中的所有内容都在您构建应用程序时编译到您的应用程序中。


2)可能的解决方案:查看动画

一种可能的解决方案是使用视图动画而不是ObjectAnimator. 使用视图动画,您可以指定分数而不仅仅是绝对值:

<set xmlns:android="http://schemas.android.com/apk/res/android">  
    <translate android:fromYDelta="-100%" android:toYDelta="0%" android:duration="1000"/>
</set>

这将为屏幕高度的View-100%到设置动画。0%换句话说,从完全的屏幕到 的位置View。你可以像这样使用它:

Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_down);
view.startAnimation(animation);

我意识到这可能对你没有任何帮助。在某些情况下,必须使用ObjectAnimators且不能使用视图动画。但是只要您能够使用视图动画,这应该可以解决您的问题。


3) 最佳实践

我们真正应该在这里解决的是我认为您的误解。正如您已经注意到,您在 xml 中定义的每个动画都有绝对的开始和结束值。这些值是静态的,在编译您的应用程序后无法更改。

如果您查看资源如何与许多可用的选择器以及 dp、sp... 一起工作,那么您将意识到它旨在做一件事:
例如,您可以定义一个将 a 移动View100dp 的动画。Dp 是物理尺寸的度量,100dp 与任何像素密度的任何屏幕的物理尺寸完全相同。通过选择器,您可以为具有更小或更大屏幕的设备更改此动画,其中动画可能移动View太多或太少。但是你只能填写静态值。除了使用选择器之外,您无法为每个设备自定义动画。

所以你看,资源真的只是为任何静态和不变的东西而设计的。它适用于尺寸或字符串转换,但有时使用动画可能会有点痛苦。正如我上面所说,只有视图动画通过提供指定分数而不是绝对值的选项来提供一种绕过静态性质的方法。但一般来说,你不能在 xml 中定义任何动态的东西。

为了进一步证实这一论点,请查看谷歌关于动画的优秀 DevBytes 视频:

然后您会注意到,这些示例中没有一个动画是在 xml 中定义的。当然有人可能会争辩说他们没有在 xml 中定义它们,因为他们希望更轻松地解释和显示代码,但在我看来,这再次证明了一点:

您不能在依赖于纯动态值的静态资源中定义动画

由于屏幕宽度/高度因设备而异,因此您需要为每个设备设置不同的动画。只有视图动画提供了一种解决方法,因为它们允许您定义分数而不是绝对值。在任何其他情况下,您都需要以编程方式定义动画。幸运的是,这并不难ObjectAnimators

Animator animator = ObjectAnimator.ofFloat(view, View.X, startValue, endValue);
animator.start(); 

这将以View像素为单位对从开始到结束值的 x 位置进行动画处理。您可以传递 的宽度View以像您想要的那样对其进行动画处理:

Animator animator = ObjectAnimator.ofFloat(view, View.X, -view.getWidth(), 0.0f);
animator.start();

这将使View从左侧滑入的动画。它完全在屏幕外开始并在其最终位置停止。你也可以这样做:

view.animate().x(targetValue);

这会将其View从当前 x 位置动画到目标 x 值。

但请不要误会我,您仍然应该尝试在资源中尽可能多地定义 - 无论是动画还是其他任何东西。应尽可能避免硬编码动画或任何其他值,除非在您的情况下有必要。


4) 总结

所以总结一下:

  • ObjectAnimators如果不将所需的 getter 和 setter 方法添加到View要制作动画的对象中,您将无法做您想做的事情。
  • 如果可能的话,使用视图动画。使用它们,您可以根据View.
  • 如果上述方法不起作用或不适用于您的情况,则以编程方式定义动画,这在任何情况下都有效。

我希望我能帮助你,如果你有任何进一步的问题,请随时提问!

于 2014-06-30T04:18:40.333 回答
3

只需根据屏幕密度创建不同的文件夹。

因此,假设您的动画 xml 位于名为 anim 的文件夹中(在 res 父文件夹中)。

然后在 res 中创建 anim-ldpi 、 anim-mdpi 等。将您各自的动画放在代表屏幕密度的每个文件夹中,android 将选择正确的。

于 2013-04-27T14:52:19.383 回答
0

有人在问我的工作解决方案。这里是。它是通过 Mono 编写的 C# 代码。希望 Java 作者能够弄清楚该语言应该是什么。

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

}
于 2016-08-13T21:25:49.043 回答