Android 动画 - 属性动画

Android 动画导图

系列文章传送门:

Android 动画 - 帧动画 & 补间动画
Android 动画 - 插值器
Android 动画 - 属性动画


属性动画

本文目录

属性动画与视图动画的区别

帧动画 和 视图动画 都只能对 View 对象添加动画效果,而且只能对公开对象属性添加效果,比如可以对 view 的缩放和旋转添加动画,但是不能操作背景颜色。

视图动画系统的另一个缺点是它只会在绘制视图的位置进行修改,而不会修改实际的视图本身。例如,如果您为某个按钮添加了动画效果,使其可以在屏幕上移动,该按钮会正确绘制,但能够点击按钮的实际位置并不会更改,必须通过实现自己的逻辑来处理此事件。

有了属性动画系统,您就可以完全摆脱这些束缚,还可以为任何对象(视图和非视图)的任何属性添加动画效果,并且实际修改的是对象本身。属性动画系统在执行动画方面也更为强健。概括地讲,您可以为要添加动画效果的属性(例如颜色、位置或大小)分配 Animator,还可以定义动画的各个方面,例如多个 Animator 的插值和同步。

不过,视图动画系统的设置需要的时间较短,需要编写的代码也较少。如果视图动画可以完成您需要执行的所有操作,或者现有代码已按照您需要的方式运行,则无需使用属性动画系统。在某些用例中,也可以针对不同的情况同时使用这两种动画系统。

如果是view 的平移、缩放等常规操作,通过View动画是完全可以实现的。

动画本质

在了解属性动画之前,看一下动画本质

动画实际上是改变 View 在某一时间点的样式属性
比如在 10 时,view 坐标是10px,在 20s 时是 20px,就有向右移动的视觉

实际上通过一个线程每个一段时间通过调用 view.setX(index ++) 值也能产生动画效果,这就是属性动画的原理。


动画是一个比较复杂的流程,需要考虑的因素比较多,在开发层面肯定不能直接调用view.setX()。

属性动画的工作方式

  • 计算属性值
  • 设置目标对象的属性值(应用产生动画效果)

属性值的计算

前面的例子简单的描述了 40ms 内 从在 0-40之间的属性值变化


如何计算动画

我们总说插值器和估值器,他们到底是怎么转化成属性的变化的?


1. 计算已完成动画 fraction
执行一个动画,会指定目标对象属性的开始值、结束值以及持续时间。在动画 start 后,会得到一个 elapsed fraction 表示当前动画已完成的百分比,范围是 0~1,表示 0%~100%。

2. 计算插值(动画变化率)
之前的已经介绍过,插值器,用来改变动画的变化速率的,在计算 elapsed fraction 之后,会根据当前设置的 TimeInterpolator 计算出一个 interpolation value。

3. 计算属性值
当插值计算完成后,ValueAnimator 会根据插值分数调用合适的 TypeEvaluator 去计算运动中的属性值。
其实就是时间与属性的映射关系。也可以自定义估值器让这个关系更加丰富和灵活。

比如:1000ms 内 0-100变化 如果是线性的、属性为整数时 time = fraction, value = start + t*(end - start)(ValueAnimator.ofInt -- IntEvalutaor)

elapsed fraction 0 0.2 0.5 0.7 1
time = fraction 0 0.2 0.5 0.7 1
value =x0 + t * (x1 - x0) 0 20 50 70 100

使用插值器 time = f^2

elapsed fraction 0 0.2 0.5 0.7 1
time = f^2 0 0.04 0.25 0.49 1
value =x0 + t * (x1 - x0) 0 4 25 49 100

使用估值器 float value = (x0 + t * (x1 - x0))/2

elapsed fraction 0 0.2 0.5 0.7 1
time = f^2 0 0.04 0.25 0.49 1
value 0 2 12.5 24.5 50

设置属性值

可以为 ValueAnimator 对象添加 AnimatorUpdateListener,通过实现 onAnimationUpdate 方法更新目标 View 的属性值,当前属性值通过 ValueAnimator#getAnimatedValue()获取。

估值器 Evaluators

定义如何通过起始和结束值计算属性值,可得出某一个时刻具体的值。

API 提供了几种 Evaluators

  • IntEvaluator:计算 int 值
  • FloatEvaluator:计算 float 值
  • ArgbEvaluator:计算 color(十六进制)属性值
  • PointFEvaluator:计算 Point
    TypeEvaluator 是一个可以自定义计算属性值的接口,上面的的集中 API 内部的Evaluators 也是实现 TypeEvaluator,如果 API 内部不满足也可以自定义类型和计算方式
public interface TypeEvaluator<T> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value.
     * @param endValue   The end value.
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public T evaluate(float fraction, T startValue, T endValue);
}

这个接口只有一个方法 evaluate(),比如,

class BulletEvaluator implements TypeEvaluator<PointF> {

    int degree;// 变化角度

    /**
     * 与 ↑ 夹角
     *
     * @param degree
     */
    public BulletEvaluator(int degree) {
        super();
        this.degree = degree;
    }

    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        double rad = degree * Math.PI / 180;
        PointF pointf = new PointF();
        pointf.y = (endValue.y - startValue.y) * fraction + startValue.y;
        pointf.x = (float) (Math.abs(((endValue.y - startValue.y) * fraction)* Math.tan(rad)) + startValue.x);
        return pointf;
    }
}

使用这个估值器

 ValueAnimator valueAnimator = ValueAnimator.ofObject(new BulletEvaluator(DEGREE), mPointFs[0], mPointFs[1]);

相关 API Animator

视图动画 使用的类都在 android.view.animation 包 下
而属性动画系统的API 在 android.animation 包下

ValueAnimator

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

start() 方法运行时,ValueAnimator 会开始计算 1000ms 时长内 0 和 100 之间的值。可以为 ValueAnimator 对象添加 AnimatorUpdateListener来使用变化的值,如以下代码所示:

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator updatedAnimation) {
    // You can use the animated value in a property that uses the
    // same type as the animation. In this case, you can use the
    // float value in the translationX property.
  float animatedValue = (float)updatedAnimation.getAnimatedValue();
  textView.setTranslationX(animatedValue);
  }
});

onAnimationUpdate() 方法中,可以使用更新后的动画值,使用在某个视图的属性中。比如实例就实现了右移的效果。

API 提供了几个方法来获取 ValueAnimator 对象(红色区域)。


除了 ofFloat()ofInt()ofArgb()(可以做颜色渐变)都可以获取 ValueAnimator 对象,使用方法与ofFloat() 一样的。这里的的入参是对应类型的可变长参数。

这里还有个ofObject() ,它的入参除了 Object 数组,还有一个 TypeEvaluator 类型的入参。TypeEvaluator 是估值器,我们可以自定义动画效果,通过执行以下操作来指定要添加动画效果的自定义类型:

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

ofProertyValuesHolder() 见下文关键帧的使用

valueAnimator 是通过监听,使用数值 value 对 view 进行属性改变,达到动画效果。为了更便于使用,API 提供了一个 ObjectAnimator,

ObjectAnimator

ObjectAnimator 是 ValueAnimator 的子类,允许指定目标对象和该对象的一个属性,这个类会根据计算得到的新值自动更新属性。

因为继承了 ValueAnimator,因此完全可以像ValueAnimator 那样调用。你看下 ObjectAnimator 的工厂方法中,重载了一些方法,比起常用的 ofxxx(),多了一些入参。

下面的写法等同于上面的示例,表示对 view 的 translationX 属性,在 1000ms 内从向右移动100px,使用更方便,不需要通过监听。

ObjectAnimator animation = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f);
animation.setDuration(1000);
animation.start();

ObjectAnimator 的自动更新功能 , 依赖于属性身上的settergetter 方法 , 所以要使 ObjectAnimator 正确更新属性,需要注意一下几点:
(言简意赅:就注意要有 setter 和 getter, 细节见下文)

  • 要添加动画效果的对象属性 必须具有 set<PropertyName>() 形式的 setter 函数(采用驼峰式大小写形式)。由于 ObjectAnimator 会在动画过程中自动更新属性,它必须能够使用此 setter 方法访问该属性。例如,如果属性名称为 foo,则需要使用 setFoo() 方法。如果此 setter 方法不存在,可以考虑如下方式:

    • 如果有权限,可添加 setter 方法到类中。
    • 使用可以更改的封装容器类,让该封装容器使用有效的 setter 方法接收值并将其转发给原始对象。
    • 改用 ValueAnimator。
  • 如果在 ObjectAnimator 的一个工厂方法中仅为 values... 参数指定了一个值,则系统会假定它是动画的结束值。因此,要添加动画效果的对象属性必须具有用于获取动画起始值的 getter 函数。getter 函数必须采用 get<PropertyName>() 形式。例如,如果属性名称为 foo,则需要使用 getFoo() 方法。

  • 要添加动画效果的属性的 getter(如果需要)和 setter 方法的操作对象必须与您为 ObjectAnimator 指定的起始值和结束值的类型相同。例如,如果构建以下 ObjectAnimator,则必须具有 targetObject.setPropName(float)targetObject.getPropName(float) :
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)

  • 根据要添加动画效果的属性或对象,可能需要对视图调用 invalidate() 方法,以强制屏幕使用添加动画效果之后的值重新绘制自身。可以在 onAnimationUpdate() 回调中执行此操作。例如,如果为可绘制对象的颜色属性添加动画效果,则仅当该对象重新绘制自身时,屏幕才会刷新。视图的所有属性 setter(如 setAlpha() 和 setTranslationX())都会使视图失效,因此,在使用新值调用这些方法时,无需使视图失效。

AnimatorSet

使用 AnimatorSet 用来编排多个动画
在一些场景,需要根据一个动画开始或结束的时间来播放另一个动画。通过相关 API,可以将动画捆绑到一个 AnimatorSet 中,来指定是同时播放动画、按顺序播放还是在指定的延迟时间后播放。也可以相互嵌套 AnimatorSet 对象。

以下代码段通过以下方式播放相应的 Animator 对象:

播放 bounceAnim。
同时播放 squashAnim1、squashAnim2、stretchAnim1 和 stretchAnim2。
播放 bounceBackAnim。
播放 fadeAnim。

    AnimatorSet bouncer = new AnimatorSet();
    bouncer.play(bounceAnim).before(squashAnim1);
    bouncer.play(squashAnim1).with(squashAnim2);
    bouncer.play(squashAnim1).with(stretchAnim1);
    bouncer.play(squashAnim1).with(stretchAnim2);
    bouncer.play(bounceBackAnim).after(stretchAnim2);
    ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
    fadeAnim.setDuration(250);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(bouncer).before(fadeAnim);
    animatorSet.start();

关键帧 Keyframe

Keyframe 对象由 time-value 的键值对组成,用于在动画的特定时间定义特定的状态。每个关键帧还可以用自己的插值器,控制前一帧和当前帧的时间间隔间内的动画。

如果想指定某一特定时间的特定状态,那么简单的使用ObjectAnimator 就不能满足了ObjectAnimator.ofInt(....) 类似的工厂方法,无法指定特定的时间点的状态。

要实例化 Keyframe 对象

  • 使用它的任一工厂方法(ofInt()、ofFloat() 或 ofObject())来获取类型合适的 keyframe。

  • 通过调用 ofKeyframe() 方法获取 PropertyValuesHolder 对象。获取对象后,您可以通过传入 PropertyValuesHolder 对象以及要添加动画效果的对象来获取 Animator。以下代码段演示了如何做到这一点:

    PropertyValuesHolder 属性持有者,相关属性值的操作以及属性的setter,getter方法的创建,属性值以 Keyframe来承载,最终由KeyframeSet 统一处理

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
    rotationAnim.setDuration(5000);

每个 KeyFrame 的 Interpolator

每个 KeyFrame 其实也有个 Interpolator 。如果没有设置,默认是线性的。之前为 Animator 设置的
Interpolator 是整个动画的,而系统允许你为每一 KeyFrame 的单独定义 Interpolator ,系统这样做的目的是允许你在某一个 keyFrame 做特殊的处理,也就是整体上是按照你的插值函数来计算,但是,如果希望某个或某些 KeyFrame 会有不同的动画表现,那么你可以为这个 keyFrame 设置 Interpolator 。
因此,Keyframe的定制性更高,你如果想精确控制某一个时间点的动画值及其运动规律,你可以自己创建特定的 Keyframe

ViewPropertyAnimator

为方便为某一个View的 多个属性添加并行动画,使用 ViewPropertyAnimator 对象就可以完成。

比如:view 的 x 和 y 同时变化,使用 ObjectAnimator + AnimatorSet 这样处理

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

使用 ObjectAnimator + PropertyValuesHolder

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

而使用 ViewPropertyAnimator 只需一行代码

myView.animate().x(50f).y(100f); // myView.animate() 直接返回一个ViewPropertyAnimator对象

动画监听器

可以使用下述监听器来监听动画播放期间的重要事件

Animator.AnimatorListener

  • onAnimationStart() - 在动画开始播放时调用。
  • onAnimationEnd() - 在动画结束播放时调用。
  • onAnimationRepeat() - 在动画重复播放时调用。
  • onAnimationCancel() - 在动画取消播放时调用。取消的动画也会调用 onAnimationEnd(),无论它们以何种方式结束。

ValueAnimator.AnimatorUpdateListener

onAnimationUpdate() - 对动画的每一帧调用。监听此事件即可使用 ValueAnimator 在动画播放期间生成的计算值。要使用该值,请查询传递到事件中的 ValueAnimator 对象,以使用 getAnimatedValue()方法获取当前添加动画效果之后的值。如果使用了 ValueAnimator ,则必须实现此监听器

根据您要添加动画效果的属性或对象,您可能需要对视图调用 invalidate(),以强制屏幕上的相应区域使用添加动画效果之后的新值重新绘制自身。例如,如果为可绘制对象的颜色属性添加动画效果,则仅当该对象重新绘制自身时,屏幕才会刷新。视图的所有属性 setter(如 setAlpha() 和 setTranslationX())都会使视图失效,因此,在使用新值调用这些方法时,您无需使视图失效。

不一定需要实现 Animator.AnimatorListener 接口的所有方法,可以使用 AnimatorListenerAdapter 类,而非实现接口。AnimatorListenerAdapter 类提供了方法的空实现,可供替换。

例如,以下代码段可仅为 onAnimationEnd() 回调创建 AnimatorListenerAdapter

ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
    public void onAnimationEnd(Animator animation) {
        balls.remove(((ObjectAnimator)animation).getTarget());
    }
}

在XML中声明属性动画

属性动画系统支持使用 XML 声明属性动画。通过在 XML 中定义动画,可以轻松地在多个 Activity 中重复使用,而且也更容易编辑。

为了将使用新属性动画 API 的动画文件与使用旧版视图动画框架的动画文件区分开来,从 Android 3.1 开始,属性动画的 XML 文件需要保存到 res/animator/ 目录中。

属性动画支持的 Tag 有:

  • ValueAnimator - <animator>
  • ObjectAnimator - <objectAnimator>
  • AnimatorSet - <set>
    以下示例依次播放两组对象动画,其中第一个嵌套集会同时播放两个对象动画:
<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

java 代码中 使用 AnimatorInflater.loadAnimator 获取 AnimatorSet 对象,调用 setTarget() 设置一个目标对象,使用 start 运行即可

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.animator.property_animator);
set.setTarget(myObject);
set.start();

标签编译后的资源对象分别为 ValueAnimator , ObjectAnimator , or AnimatorSet

XML文件的根元素必须为 <set> , <objectAnimator> , or <valueAnimator> 之一。也可以在一个 set 中组织不同的动画,包含其它 <set> 元素,也就是说,可以嵌套

<set 
android:ordering=["together" | "sequentially"]>  <!-- 启动方式是先后顺序还是有同时(default)的-->
    
<!--propertyName 属性名-->
<objectAnimator 
    android:propertyName="string" 
    android:duration="int" 
    android:valueFrom="float | int | color" 
    android:valueTo="float | int | color" 
    android:startOffset="int" 
    android:repeatCount="int" 
    android:repeatMode=["repeat" | "reverse"] 
    android:valueType=["intType" | "floatType"]/> 
<animator 
    android:duration="int" 
    android:valueFrom="float | int | color" 
    android:valueTo="float | int | color" 
    android:startOffset="int" 
    android:repeatCount="int" 
    android:repeatMode=["repeat" | "reverse"] 
    android:valueType=["intType" | "floatType"]/> 
<set> 
... 
</set> 
</set>  
  • objectAnimator 元素没有暴露 target 属性,因此不能够在
    XML中执行一个动画,必须通过调用 loadAnimator() 填充你的XML动画资源,并且调用 setTarget() 应
    用到拥有这个属性的目标对象上

还可以在 XML 中声明 ValueAnimator

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

要使用代码中的上一个 ValueAnimator,扩充为 ValueAnimator 对象、添加 AnimatorUpdateListener、更新属性,如下面的代码所示:

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue =(float)updatedAnimation.getAnimatedValue();
            textView.setTranslationX(animatedValue);
        }
});
xmlAnimator.start();

参考:https://developer.android.com/guide/topics/graphics/prop-animation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343