Android 基础动画之属性动画详解

上两篇文章主要介绍了 Android 基础动画之帧动画 以及 Android 基础动画之补间动画 。本篇文章主要介绍的是Android基础动画之 属性动画

补间动画 这篇文章的末尾有说道,补间动画执行完毕以后,加载的view实际上是没有点击事件的,因为点击事件依旧附着在原来的view位置,所以这种动画的完整体验有点蜜汁尴尬。为了解决这种设计之初带来的体验问题,Android3.0以后开始引入属性动画来完美解决这一历史遗留问题。

属性动画的强大之处在于,它可以作用到任何对象(不仅仅针对视图View对象),另外,属性动画还可以自定义各种动画效果(不仅仅是平移、旋转、缩放、透明度的变化)。那么属性动画是如何做到这些功能的?它的原理是在一定时间内,不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。

说完了属性动画的作用和优点,下面就用代码去学习掌握属性动画。其中属性动画有两个非常重要的类:分别是ValueAnimator 类、ObjectAnimator 类。

ObjectAnimator:

ObjectAnimator简单理解是直接对对象的属性值进行改变操作,从而实现动画效果。ObjectAnimator本质是通过不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果。ObjectAnimator一般推荐是代码进行使用(下面是基本代码和一些常见的API):

        //目标view  
        TextView mTextView = findViewById(R.id.ob_text);

        //平移动画
        ObjectAnimator translationAnimator = ObjectAnimator.ofFloat(mTextView, "translationX", 200);
        
        // 设置动画运行的时长
        translationAnimator.setDuration(500);

        // 设置动画延迟播放时间
        translationAnimator.setStartDelay(500);

        // 设置动画重复播放次数 = 重放次数+1 默认是0
        // 动画播放次数 = infinite时,设置为 -1 动画无限重复
        translationAnimator.setRepeatCount(-1);

        // 设置重复播放动画模式
        // ValueAnimator.RESTART(默认):正序重放
        // ValueAnimator.REVERSE:倒序回放
        translationAnimator.setRepeatMode(ValueAnimator.RESTART);

        //开始动画
        translationAnimator.start();

        //旋转动画
        ObjectAnimator rotation = ObjectAnimator.ofFloat(mTextView,"rotation",90);
        rotation.setDuration(500);
        rotation.start();

        //缩放动画
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(mTextView,"scaleX",1.5f);
        scaleX.setDuration(500);
        scaleX.start();

        //透明度动画
        ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextView,"alpha",0.2f);
        alpha.setDuration(500);
        alpha.start();
        

你可能会问,那我想通过ObjectAnimator来同时实现多个效果一起运行,那该怎么办?
如果想通过一个ObjectAnimator同时改变多个属性,则需要使用PropertyValuesHolder,参考代码如下:


        PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("translationX",200);
        PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("translationY",200) ;
        PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("rotation",90) ;
        PropertyValuesHolder holder4 = PropertyValuesHolder.ofFloat("scaleX",1.5f) ;
        PropertyValuesHolder holder5 = PropertyValuesHolder.ofFloat("alpha",0.2f) ;

        //ObjectAnimator.ofPropertyValuesHolder()
        //参数一:目标view
        //参数二:可变参数 支持多个PropertyValuesHolder
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, holder1, holder2,holder3,holder4,holder5);
        
        animator.setDuration(500);
        
        animator.start();

下面在来看看ValueAnimator

ValueAnimator

ValueAnimator实现动画的原理是通过不断控制值的变化,然后手动赋给对象的属性,从而实现动画效果。ValueAnimator的大致流程是,先指定将初始值以何种数值(整型、浮点型)的形式 过渡到结束值;接着,开发者手动将值,赋值给目标的属性值。接下来的步伐涉及到了别的内容(插值器与估值器)但还是会慢慢分析。

ValueAnimator重要的方法我基于面向对象的设计原则将其分为两类三个(本质是一类),当然这是我自己的理解。既然是面向对象的设计语言,那么第一步肯定是 new 对象,那么ValueAnimator这个类 new 对象的第一种姿势是这样的:

        /**
         * ValueAnimator创建对象姿势一:
         * 调用ofInt(int...)、ofFloat(float...)
         * 形参是可变参数、可传多个参数
         * 将传入的多个Int参数进行平滑过渡:假设此处传入0和3,表示将值从0平滑过渡到3
         * 以此类推如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,
         */
        ValueAnimator animInt = ValueAnimator.ofInt(0, 3);

        ValueAnimator animFoat = ValueAnimator.ofFloat(0, 3);

既然拿到了ValueAnimator实例对象以后,接下来就调用对象提供的方法实现具体的功能( 下面是参考代码 )

        textView = findViewById(R.id.ob_text);

        ValueAnimator animInt = ValueAnimator.ofInt(0, 3);

        // 设置动画运行的时长
        animInt.setDuration(500);

        // 设置动画延迟播放时间
        animInt.setStartDelay(500);

        // 设置动画重复播放次数 = 重放次数+1
        // 动画播放次数 = infinite时,动画无限重复
        animInt.setRepeatCount(0);

        // 设置重复播放动画模式
        // ValueAnimator.RESTART(默认):正序重放
        // ValueAnimator.REVERSE:倒序回放
        animInt.setRepeatMode(ValueAnimator.RESTART);

        animInt.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = animation.getAnimatedFraction();
                float value = (float) animation.getAnimatedValue();
                Log.i("app","属性值:"+"fraction:"+fraction+",value:"+value);

                //在这里设置具体的动画属性值
                textView.setTranslationX(value);

            }
        });

        //开启动画
        animInt.start();

说完了第一种new ValueAnimator对象的姿势,现在说第二种:


        /**
         * ValueAnimator实例化对象姿势二:
         * 参数一:TypeEvaluator 估值器
         * 参数二:Object... values 可变参数,可以传入具体的动画对象(开始-结束)
         */

        ValueAnimator valueAnimator = ValueAnimator.ofObject(new TypeEvaluator() {
            @Override
            public Object evaluate(float fraction, Object startValue, Object endValue) {
                return null;
            }
        },"","");
        

可以看到,通过ofObject这个函数实例化ValueAnimator对象的时候,需要我们传入一个TypeEvaluator,
以及一个可变参数Object,下面就这2个参数着重说明:

参数一:TypeEvaluator(估值器)

这个就是我们经常提到的估值器。估值器和插值器是很多开发容易搞混淆的一个概念,面试的时候也会问到这个(因为自定义控件会附加动画的内容)本着不抛弃不放弃的精神、下面就对这两个概念进行详尽分析:

首先,这个TypeEvaluator(估值器)本质是一个接口,源码如下:


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

}

其实,通过源码的英文注释就可以分析出这个估值器的实际作用(英文注释翻译过来就是):这个函数返回线性插值起始值和结束值的结果。其中参数fraction代表起始值和结束值之间的比例。简单的计算公式是:result = x0 + t * (x1 - x0);参数的具体是指:x0=startValue;x1=endValue;t=fraction最后,这个方法返回的是,在开始和结束值之间的线性插值,给定分数参数。

简单点说就是,估值器是通过计算公式来进行的值的计算。

还有一点,之前说的ValueAnimator.ofFloat()以及ValueAnimator.ofInt(),这2个方法内部实际上由系统已经设置好了对应的估值器,分别是FloatEvaluator以及IntEvaluator,所以虽然是“两类三个”,但本质上来说还是属于"一类"。

另外既然我们知道了计算公式、那么就可以根据业务定制自己的估值器,下面是参考拓展代码:

    /**
     * TypeEvaluator里面的泛型可以根据业务去订制
     */
    class MyEvaluator implements TypeEvaluator<Object>{

        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            /**
             * 计算公式自己随意拓展
             */
            return null;
        }
    }

接着将这个自定义的估值器设置到我们的ValueAnimator即可:

 ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyEvaluator());

说完了TypeEvaluator我们在来看看插值器:Interpolator:

Interpolator(插值器)

依旧打开源码目睹下庐山真面目:

/**
 * An interpolator defines the rate of change of an animation. This allows
 * the basic animation effects (alpha, scale, translate, rotate) to be 
 * accelerated, decelerated, repeated, etc.
 */
public interface Interpolator extends TimeInterpolator {
    // A new interface, TimeInterpolator, was introduced for the new android.animation
    // package. This older Interpolator interface extends TimeInterpolator so that users of
    // the new Animator-based animations can use either the old Interpolator implementations or
    // new classes that implement TimeInterpolator directly.
}

嗯,英文注释非常详细,首先是类注释,翻译过来如下:差值器定义了动画的变化速率。这允许基本的动画(透明、缩放、平移、旋转)效果可以加速,减速,重复,等等。关于接口方法内的注释主要是关于新旧版本TimeInterpolator的一些说明。

另外,笔者的Android SDK系统版本是26,所以源码可能会有一些不同。

透过英文注释可以得知:插值器的功能主要是为了丰富(透明、缩放、平移、旋转)这些动画的效果。比如,我们可以通过插值器去设置炫丽的效果,让以往的动画生冷平稳的过渡效果成为历史,让动画变的更立体、更有灵魂。也就是让动画效果变化的模式更客观

系统也为我们提供了一些默认的插值器方便我们使用:


插值器

那么,如何使用系统为我们提供好的插值器?声明一个插值器有两种写法:

  • 写法一:通过xml文件,对标签的内容进行编写

首先: res/animator的文件夹路径内里创建相应的动画xml

接着:代码编写

<animator xmlns:android="http://schemas.android.com/apk/res/android"  

    // 初始值
    android:valueFrom="0" 
    
    // 结束值
    android:valueTo="100"  
    
    // 变化值类型 :floatType & intType
    android:valueType="intType" 

    // 动画持续时间(ms),必须设置,动画才有效果
    android:duration="3000" 

    // 动画延迟开始时间(ms)
    android:startOffset ="1000"
    
    // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
    android:fillBefore = “true” 
    
    // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
    android:fillAfter = “false” 
    
    // 是否应用fillBefore值,对fillAfter值无影响,默认为true
    android:fillEnabled= “true” 
    
    // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
    android:repeatMode= “restart” 

    // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
    android:repeatCount = “0” 

    // 插值器  这里等价于OvershootInterpolator
    android:interpolator = "@android:anim/overshoot_interpolator"

/>

其中,这里的 android:interpolator 标签代表的就是Java中的OvershootInterpolator这个系统为我们写好的插值器

  • 写法二:Java代码编写
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyEvaluator());

        Animation animation = new AlphaAnimation(1,0);
        //调用setInterpolator 使用插值器
        animation.setInterpolator(new OvershootInterpolator());

那系统自带的插值器代表的意思是什么?

Interpolator 资源ID 功能
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速再减速
AccelerateInterpolator @android:anim/accelerate_interpolator 加速
AnticipateInterpolator @android:anim/anticipate_interpolator 先后退一小步然后加速前进
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 先后退一小步再加速前进,超出终点一小步再回到终点
BounceInterpolator @android:anim/bounce_interpolator 最后阶段弹球效果
CycleInterpolator @android:anim/cycle_interpolator 周期运行
DecelerateInterpolator @android:anim/decelerate_interpolator 减速
LinearInterpolator @android:anim/linear_interpolator 匀速
OvershootInterpolator @android:anim/overshoot_interpolator 快速到达终点并超出一小步然后回到终点

嗯,以上九个插值器所带来的各种效果是很丰富的(比如,最后一个阶段有弹球的效果;或者先加速再减速),很多博客和资料也是说的是九个。如果你细心看完上面那张系统父子关系层级截图,你会发现系统明明为我们生成了10个默认的插值器,但是你这里只有9个?难道是写漏了一个?嗯,多余的一个是PathInterpolator

打开PathInterpolator源码可以看到它的两个构造函数,生成的path其实是一个贝赛尔曲线。有兴趣的小伙伴可以自行查阅资料去了解该插值器的使用和说明。

回到正题,一般来说,系统为我们提供的插值器基本上就可以满足开发需求了,如果还是不能满足我们就可以自定义插值器:

 class MyInterpolator implements Interpolator{

        @Override
        public float getInterpolation(float input) {
            /**
             * 在这里做自己想要做的逻辑
             */
            return 0;
        }
    }

关于自定义插值器里面的getInterpolation(float input)这个方法需要说明一下,这里的input的取值范围是0 - 1,另外,在这个方法内部我们就可以根据业务进行自己想要的计算。

总结:

属性动画是Android3.0之后出现的动画,不仅解决了之前的历史遗留问题,而且通过搭配估值器与插值器的效果让我们的动画更加立体形象、丰富多彩。

如果这篇文章对您有开发or学习上的些许帮助,希望各位看官留下宝贵的star,谢谢。

Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用,也希望大家尊重笔者的劳动成果

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

推荐阅读更多精彩内容