Android动画之属性动画

概述

Android属性动画是Android 3.0之后添加的一个非常强大的动画。与视图动画不同的地方在于,属性动画能够真正的改变View的属性。例如:如果我们通过视图动画移动了一个View,那么这个View真正的位置并没有发生改变,它的点击事件依然保留在原来的位置。而如果我们通过属性动画改变了一个View的位置,那么它真实的位置也就改变了。此外属性动画并不局限于对View实现动画,它几乎可以对任何一个对象的任何属性实现动画。

Animator三个子类

通常我们实现属性动画并不需要直接使用Animator,而是使用它的以下三个子类:

  • ObjectAnimator
  • ValueAnimator
  • AnimatorSet

ObjectAnimator

一般我通过ObjectAnimator的工厂方法ofFloat()来获得它的实例(其他的方法还有ofInt()ofArgb())。

例如下代码实现了一个位移动画:

ObjectAnimator translate = ObjectAnimator.ofFloat(view, "translationX", 200f);
translate.setDuration(1000);  
translate.start();
ofFloat()参数说明
  1. 第一参数是一个需要实现动画的View。
  2. 第二个参数是需要操作的属性。
  3. 最后一个其实是可变参数。用来表示属性值的一系列取值。当只传一个值的时候,表示从初始值,变化到该值。
常用属性值
  • translationXtranslationY: 控制View距离左边和顶部的距离的增加值。是一个相对值。
  • rotationrotationXrotationY: rotation是控制View围绕其支点进行旋转。rotationXrotationY分别是围绕X轴和Y轴旋转。
  • scaleXscaleY: 控制View的缩放。
  • pivotXpivotY: 控制View的支点位置,进行旋转和缩放,默认是View的中点。它们都是float值,0表示View的最左边和最顶端,1表示最右端和最下端。
  • alpha: 控制View的透明度。
  • xy: 控制View在布局容器中距离左边和顶部的距离。是一个绝对值。

注意

当用属性动画操作一个View的属性时,这个View必须具有相应的get和set方法。

ValueAnimator

ValueAnimatorObjectAnimator的父类。它有点像一个数值发生器。不提供任何动画效果。通常我们需要在onAnimationUpdate获取当前时间点发生的值,然后操作对象的属性,以实现动画效果。

如下代码实现了一个倒计时效果:

ValueAnimator countDown = ValueAnimator.ofInt(10, 0);
countDown.setDuration(10000);
countDown.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    textView.setText("" + animation.getAnimatedValue());
    }
});
countDown.start();

补充
系统大约每10ms刷新一次,来更新属性值。所以如果我们要做一个以秒为单位的计时器,可以直接用这种效果实现。

AnimatorSet

如果我们要对一个对象的多个属性实现动画,通常有如下两种做法:

  • 使用PropertyValuesHolder
  • 使用AnimatorSet
使用PropertyValuesHolder
PropertyValuesHolder rotate = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 2.0f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 2.0f);
ObjectAnimator together = ObjectAnimator.ofPropertyValuesHolder(view, rotate, scaleX, scaleY);
together.setDuration(1000);
together.start();
使用AnimatorSet
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).setDuration(1000);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 2.0f).setDuration(1000);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, 2.0f).setDuration(1000);
AnimatorSet set = new AnimatorSet();  
set.playSequentially(rotate, scaleX, scaleY);
set.setDuration(1000);
set.start();

补充
AnimatorSet可以通过playSequentially(),playTogether(),来控制动画的串行和并行。通过set.play().with()、before()、after()等方法可以实现动画的多种协同效果。

操作一个没有set和get的属性

上文已经提到当用属性动画操作一个View的属性时,这个View必须具有相应的get和set方法。那么如果我们想操作一个没有get和set的属性,该怎么办呢?
通常有三种实现方式:

  • 自己创建一个属性类。
  • 通过一个包装类来实现。
  • 通过ValueAnimator来实现。

通过包装类实现

当我们查看View的set和get方法时,它只有scaleX和scaleY,而没有scale属性。有时候,我们需要对一个View的X和Y都进行缩放,又不想使用两次ObjectAnimator,就可以通过如下方法实现。

如下代码是一个包装类的示例:

public class WrapperView {
    private View target;
    private float scale;
    public WrapperView(View target){
        this.target = target;
    }
    public setScale(float scale){
        this.scale = scale;
        target.setScaleX = scale;
        target.setScaleY = scale;
    }
    public float getScale(){
        return scale;
    }
}

使用该包装类直接实现对View的缩放:

WrapperView target = new WrapperView(view);
ObjectAnimator scale = ObjectAnimator.ofFloat(target,"scale",1f,2f);
scale.setDuration(1000);  
scale.start();

通过ValueAnimator实现

如果我们想实现一个View在一个曲线运动的动画,就可以通过如下方法实现。
(曲线公式为:y=-1/150x^2+4x )

public static final int TRANS_X = 600;
ValueAnimator animator = new ValueAnimator();
float offsetX = view.getX();
float offsetY = view.getY();
animator.setDuration(2000);
// 和ofFloat类似,设置属性的起始值和结束值。
animator.setObjectValues(new PointF(offsetX, offsetY), new PointF(TRANS_X + offsetX, offsetY));
// 设置自定义的Evaluator.
animator.setEvaluator(new TypeEvaluator<PointF>() {
    @Override
    // api level 21以上已经实现了PointFEvalutor.
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        PointF pointF = new PointF();
        float d = fraction * TRANS_X;
        pointF.x = startValue.x + d;
        pointF.y = startValue.y + (1.0f / 150f) * d * d - 4 * d;
        return pointF;
    }
});
// 通过监听器得到相应的Evaluator值,并应用。
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        PointF pointF = (PointF) animation.getAnimatedValue();
        view.setX(pointF.x);
        view.setY(pointF.y);
    }
});
animator.start();

说明
通常如果我们需要操作一个不存在的属性,我们需要自定义一个Evaluator。通过这个Evaluator在动画运行的过程中生成相应的值。然后给ValueAnimator设置一个监听器,通过getAnimatedValue()来得到相应的值。然后根据这个值,做相应的操作。

关于Evaluator

Evaluator翻译过来是估值器的意思,实际上它是一种计算属性值的算法。

  • 系统定义的Evaluator有ArgbEvaluator, FloatEvaluator, IntEvaluator, PointFEvaluator等。
  • 我们自定义Evaluator只需要实现TypeEvaluator接口,并重写T evaluate(float fraction,T startValue,T endValue)方法即可。其中fraction是根据设置的Interpolator生成的(系统默认的Interpolator是AccelerateDecelerateInterpolator,即先加速后减速。),它表示变化的百分比(0表示0%,1表示100%)。例如:如果我们将一个View从(0,0)移动到(0,100),duration是1000ms,Interpolator为LinearInterpolator。那么在动画执行到500ms时,这个fraction就等于0.5。

在xml中使用属性动画

属性动画的保存在res/animator文件夹下。
如下代码定义了一个AnimatorSet:

xml文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="sequentially">
    <objectAnimator
        android:duration="2000"
        android:propertyName="translationY"
        android:valueTo="-200f"
        android:valueType="floatType"/>

    <set android:ordering="together">
        <objectAnimator
            android:duration="2000"
            android:propertyName="scaleX"
            android:valueTo="2.0f"
            android:valueType="floatType"/>
        <objectAnimator
            android:duration="2000"
            android:propertyName="scaleY"
            android:valueTo="2.0f"
            android:valueType="floatType"/>
    </set>
</set>

在Java代码中调用

Animator animator = AnimatorInflater.loadAnimator(this, R.animator.animator);
animator.setTarget(view);
animator.start();

通过KeyFrame定义动画

// 这里ofFloat(),的第一个参数表示动画完成的百分比。
Keyframe keyframe1 = Keyframe.ofFloat(0f, 0f);
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 360f);
Keyframe keyframe3 = Keyframe.ofFloat(1.0f, 0f);
PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe("rotation", keyframe1, keyframe2, keyframe3);
ObjectAnimator rotate = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
rotate.setDuration(2000);
rotate.start();

动画事件的监听

我们可以使用AnimatorListen接口来监听动画的Start、Repeat、End、Cancel。但是这样做会过于繁琐,有时候我们只想监听动画的部分事件,这时候我们可以使用AnimatorListenAdapter接口。

直接使用View的animate方法

view.animate()
    .alpha(0)
    .y(300)
    .setDuration(1000)
    .withStartAction(new Runnable(){
        @Override
        public void run(){
        }
    })
    .withEndAction(new Runnable(){
        @Override
        public void run(){
            runOnUiThread(new Runnable(){
            @Override
            public void run(){
            }
            });
        }
    }).start();  

注: 本文主要参考有:网易微专业课程、Android群英传。当然还有Api guide。

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

推荐阅读更多精彩内容