属性动画(Property Animation)

内容如下:

  • 属性动画与补间动画的不同
  • 属性动画工作原理
  • API 概述
  • 计算器 Evaluators
  • 插值器 Interpolators
  • 使用ValueAnimator
  • 使用ObjectAnimator
  • AnimatorSet
  • 动画监听
  • 视图容器中布局的动画改变
  • 使用TypeEvaluator
  • 使用插值器 (Interpolators)
  • 关键帧
  • ViewPropertyAnimator
  • 声明XML

属性动画与补间动画的不同

补间动画只提供了运动可视对象的能力,如果你想操作非可视对象,就不得不自己用代码实现。实际上,补间动画具有局限性,它只能够使一个视图对象的几个方面做运动,比如:视图的缩放、旋转,而不能操作背景颜色等等。

补间动画另一个缺点是:它只能够改变视图绘制的位置,并不能改变它的真实位置。例如:你在屏幕中运动一个按钮,按钮将被正确的绘制,但是当你点击当前位置的按钮时,按钮并不会有任何改变。所以你不得不通过实现自己的逻辑来操作它。

对于属性动画而言,这些限制被全部移除。你能够使任何对象(可视和非可视)的任何属性运动,并且对象自身被修改。属性动画就是通过不停修改属性值实现动画效果的。属性动画在实现动画方面更加强大。你可以根据需求将动画分派给对象的属性,比如:颜色、位置、大小;你也可以定义动画的差值器以及多动画的同步。

不管怎样,补间动画的创建可以用更少的时间和更少的代码。如果补间动画能够完成你需要的每件事,或者代码中已存在的补间动画代码能够实现你的需求。你就没必要使用属性动画。通过不同的使用场景来确定使用两种动画系统的哪一种。


属性动画工作原理

图1描述了一个假想的对象,沿x轴运动,表示它在屏幕上的水平位置。在40ms内运动40px,每10毫秒(默认帧刷新率),对象水平移动10像素。到40ms时,动画结束。对象停在40px处。这是一个线性插值动画的例子,表示对象匀速运动。

image.png

还可以指定具有非线性插值的动画。图2假设一个对象,在动画开始时加速,然后减速动画结束时。该对象仍然在40ms移动40px,但非线性。

image.png

属性动画系统的重要组件如何计算上面提到的动画:


image.png

ValueAnimator 对象保持跟踪动画的时间,比如动画的已运动时间和动画的当前值。
TimeInterpolator 定义动画的插值器。
TypeEvaluator 定义如何计算动画属性的值。

开始一个动画:

  • 创建一个ValueAnimator并给它的开始属性值、结束属性值以及动画的持续时间。
  • 调用start()开始动画。
  • ValueAnimator计算一个在0和1之间的逝去分数,基于动画的持续时间和经过的多少时间。该分数表示动画完成的时间百分比,0表示0%,1表示100%。
  • 当ValueAnimator完成计算一个逝去分数,它调用当前设置的TimeInterpolator计算插值分数。
  • 当插值分数计算完成,ValueAnimator调用合适的TypeEvaluator,计算该属性的值,基于插值分数、起始值和动画结束值。

API 概述

  • ValueAnimator 属性动画的计时引擎,通过属性值进行动画处理。包括计算动画值的核心功能,动画随时间变化的细节,动画重复监听以及更新时间,自定义evaluate。属性动画包括两部分:1.计算动画值;2.对正在进行动画的对象设置这些值。ValueAnimator不能直接完成第二部分,所以必须在UpdateListener中更新属性值。

  • ObjectAnimator ValueAnimator的子类,可以直接设置目标对象和属性进行动画。ObjectAnimator使用起来更方便,也有限制,对于进行的动画的属性必须要有setter,getter的访问方法。

  • AnimatorSet 提供一种将多动画分组并使之关联运行的机制。可以使动画同时执行,顺序执行,延时执行。


估算器 Evaluators

  • IntEvaluator 计算int型属性
  • FloatEvaluator 计算float型属性
  • ArgbEvaluator 计算16进制的颜色值
  • TypeEvaluator 自定义计算器(更多见下文)

插值器 Interpolators

  • AccelerateDecelerateInterpolator 先加速后减速
  • AccelerateInterpolator 加速
  • AnticipateInterpolator 向相反运动一小段后再开始运行
  • AnticipateOvershootInterpolator 前后都超出一小段
  • BounceInterpolator 结束时振动结束
  • CycleInterpolator 往返运行动画
  • DecelerateInterpolator 减速运行
  • LinearInterpolator 匀速运行
  • OvershootInterpolator 结束时超出一小段
  • TimeInterpolator 自定义

使用ValueAnimator

示例:使用ValueAnimator做曲线运动

通过调用ofInt(),ofFloat(),ofObject()方法获得一个ValueAnimator,如下:

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

还可以通过以下操作指定一个自定义类型来进行动画:

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

上述代码,当调用start()时,ValueAnimator 将在1s内计算0-1之间的动画值。它只是在计算一个值并没有影响到一个具体的对象。你需要实现ValueAnimator.AnimatorUpdateListener这个接口,通过调用getAnimatedValue()方法可以获得特定帧的计算值,通过这个值可以对目标对象进行需要的变换。

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

使用ObjectAnimator

示例:卫星菜单

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

拥有setter,getter方法的属性都可以进行动画操作。
经常操作的属性:

"alpha" 透明度;

"translationY" 沿Y轴平移;

"translationX" 沿X轴平移;

"scaleX" 横向缩放;

"scaleY" 纵向缩放;

"rotation" 平面旋转;

"rotationX" 关于X轴旋转;

"rotationY" 关于Y轴旋转;


AnimatorSet

你可以使用AnimatorSet控制多个Animator的播放顺序,同时播放,顺序播放,谁在前谁在后等。可以嵌套使用。例:

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

常用方法:
playSequentially (Animator...) 顺序播放;
playTogether (Animator...) 一起播放;

Builder方式:
play(Animator a);
创建一个Builder对象;

例1:有四个动画anim1, amin2, anim3,anim4。anim1和anim2同时播放;当anim2播放结束时播放anim3;当anim3播放结束时播放anim4。

 AnimatorSet s = new AnimatorSet();
 s.play(anim1).with(anim2);
 s.play(anim2).before(anim3);
 s.play(anim4).after(anim3);

动画监听

Animator.AnimatorListener

  • onAnimationStart() 动画开始时
  • onAnimationEnd() 动画结束时
  • onAnimationRepeat() 动画重复时
  • onAnimationCancel() 动画取消时,同时调用onAnimationEnd()

ValueAnimator.AnimatorUpdateListener

  • onAnimationUpdate() 每帧动画都将被调用
    通过getAnimatedValue()方法获取动画值。

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

视图容器中布局的动画改变

使用TypeEvaluator

如果你想以系统未知的方式运动,你可以通过实现TypeEvaluator接口来创建。实现TypeEvaluator接口只需要实现一个方法evaluate()。如下:

public class FloatEvaluator implements TypeEvaluator {
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

例:使TextView沿正弦曲线运动

创建包装类:

   private class TextHolder
        private TextView view;
        public TextHolder(TextView textView){
            view=textView;
        }
        public XYHolder getXY() {
            return new XYHolder(view.getX(),view.getY());
        }

        public void setXY(XYHolder xyHolder) {
            view.setX(xyHolder.getX());
            view.setY(xyHolder.getY());
        }
    }

估值器:

public class SineTypeEvaluator implements TypeEvaluator<XYHolder> {
    @Override
    public XYHolder evaluate(float fraction, XYHolder startValue, XYHolder endValue) {
        float xHolder=startValue.getX()+fraction*(endValue.getX()-startValue.getX());
        float yHolder=(float) (300*Math.sin(xHolder/100)+endValue.getY());
        return new XYHolder(xHolder,yHolder );
    }
}

开始动画:

ObjectAnimator objectAnimator=ObjectAnimator.ofObject(new TextHolder(mText1),"xY",new 
SineTypeEvaluator(),new XYHolder(0f,400f),new XYHolder(628f,400f));
        objectAnimator.setDuration(2000);
        objectAnimator.start();

使用插值器

一个随时间计算动画值的函数

AccelerateDecelerateInterpolator

public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

public float getInterpolation(float input) {
    return input;
}

关键帧

例如:

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

ViewPropertyAnimator

ViewPropertyAnimator提供一种更简单的多属性运动的方式。如下对比:

使用多个ObjectAnimator:

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 pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

使用ViewPropertyAnimator:

myView.animate().x(50f).y(100f);</pre>

声明XML

将xml文件保存在res/animator/ 目录下

动画类与xml标签的对应关系:
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>

运行上述动画:

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

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

推荐阅读更多精彩内容