Android动画系列——属性动画

属性动画(Animator)是在API11(Android3.0)新加入的。主要是针对一个对象的属性变化加入动画效果。属性动画的默认时长是300ms,默认帧率是10ms/帧。其可以达到的效果就是,在一定的时长内,对象的某个属性值连续发生变化。因此,属性动画几乎无所不能。只要对象有这个属性(主要是有这个属性的setter方法),它都能实现动画效果。但属性动画是Android3.0以后才加入的,这就限制了属性动画在API 11之前的系统上使用。不过,使用开源动画库nineoldandroids,可以在API 11之前的系统上使用属性动画。
简单提一下,nineoldandroids开源动画库,它的功能和android.animation.*中的类的功能完全一样,使用方式也完全一致。实现属性动画比较常用的类有:ValueAnimator,ObjectAnimator,AnimatorSet。其中ObjectAnimator是ValueAnimator的子类,一个ObjectAnimator对象可以作用于同一个对象(如View)的一个或多个属性。AnimatorSet是一组Animator的集合,主要用于以不同的顺序控制多个属性动画Animator的执行。
下面是几种属性动画的简单实现方式:

//方式一
intentBtn.animate().translationXBy(800);
//方式二
ObjectAnimator.ofInt(intentBtn,"translationX",0,800).start();
//方式三
animate(intentBtn).setDuration(1000).rotationYBy(720).x(100).y(100);

AnimatorSet的简单用法:


AnimatorSet set = new AnimatorSet();
set.playTogether(
    ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
    ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
    ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
    ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
    ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
    ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
    ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
    ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();

ValueAnimator的用法

ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            IntEvaluator evaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
              int currentCount = (int)animation.getAnimatedValue();
              float fraction = currentCount/100f;
              intentBtn.getLayoutParams().width = evaluator.evaluate(fraction,300,800);
              intentBtn.requestLayout();
             }
          });
          valueAnimator.setDuration(5000).start();

属性动画的原理

属性动画要求动画作用的对象能提供该属性的get和set方法,属性动画根据你设置的属性的初始值和结束值,以动画的效果多次调用该属性的set方法,每次传递给set方法的值都不一样,确切地说是,随着时间的推移,所传递的值越来越接近结束值。所以,你对一个对象(如View)的属性xxx做动画,如果想让动画生效,要同时满足两个条件:

  • 对象必须提供setXxx方法,如果动画初始化时没有设置起始值,还需要提供getXxx方法,因为系统要去拿到xxx属性的初始值。(如果条件不满足,会导致crash)
  • 对象的setXxx方法对属性xxx的改变必须通过某种方式体现出来,比如会带来UI的改变之类的,既然是动画,肯定是要有直观的视觉变化(如果这条不满足,动画效果提现不出来但不会crash)

如果对象没有属性的setter方法或者setter方法不能带来UI上的改变,就需要其他的方法。google告诉我们三种方法:

  1. 如果对象没有setter和getter方法,就自己设置setter和getter方法,前提是你有权限。
  2. 一个类来包装原始对象,间接为其提供getter和settter方法。
  3. 采用ValueAnimator,监听动画过程,自己实现属性的改变。

以上的第一点很显而易见,第三点在上面ValueAnimator的用法中已经讲到。着重讲讲第二点。以FrameLayout的marginTop属性为例。首先我们应该知道marginTop属性对应xml里面的android:layout_marginTop属性,也就是说它是FrameLayout控件的LayoutParams中的属性LayoutParams.topMargin,FrameLayout类中并没有setMarginTop方法,根据上面说的第二种方法,我们可以这样实现:
首先,定义一个ViewWrapper类

class ViewWrapper{
        View target;
        public ViewWrapper(View view){
            this.target = view;
        }
        public int getMarginTop(){
            return ((FrameLayout.LayoutParams)target.getLayoutParams()).topMargin;
        }
        public void setMarginTop(int marginTop){
            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) target.getLayoutParams();
            lp.topMargin = marginTop;
            target.setLayoutParams(lp);
            //注意此处只设置setLayoutParams并不能带来UI上的改变,必须在最后调用requestLayout
            //这就是属性动画原理中说到的第二个条件
            target.requestLayout();
        }
    }

然后,将这个包装对象作为ObjectAnimator的作用对象

//此处的this,是一个自定义的FrameLayout子类
viewWrapper = new ViewWrapper(this);
ObjectAnimator animator = ObjectAnimator.ofInt(viewWrapper,"marginTop",-100);
animator.setDuration(1000);
animator.start();

以上就是针对对象属性没有setter方法,实现属性动画的一种方法。

ViewPropertyAnimator

ViewPropertyAnimator的功能和ObjectAnimator一样,sdk给出的说明是,如果只是对view的一两个属性设置属性动画,就直接用ObjectAnimator就好;而如果是对一个view对象的多个属性设置动画同时开启,那么官方推荐用ViewPropertyAnimator,因为它会对这些属性动画进行自动优化。同时,使用ViewPropertyAnimator设置属性动画也是一种更方便的方式,它的API看起来更直观。

intentBtn.animate().translationXBy(800).scaleX(1.5f).alpha(0.8f);

animate()方法会创建并返回一个ViewPropertyAnimator对象,这就是ViewPropertyAnimator的简单用法。以下图表列出的是ViewPropertyAnimator的一些API和与之对应的View动画的相关的API的简单说明


image.png
PropertyValuesHolder

PropertyValuesHolder的用途,最简单的就是,在一个ObjectAnimator对象中,设置多个属性动画。我们从PropertyValuesHolder的实例化函数可以看出来,它和对应ObjectAnimator中对应的同名函数,唯一的区别就是少了一个参数,也就是属性动画的对象。

public static PropertyValuesHolder ofFloat(String propertyName, float... values)  
public static PropertyValuesHolder ofInt(String propertyName, int... values)   
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values)  

同时我们也可以看到,ObjectAnimator类有一个实例化的方法是ofPropertyValuesHolder,可以传一个或多个PropertyValuesHolder对象作为参数

public static ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)  

所以,PropertyValuesHolder和ObjectAnimator组合起来的用法就像这样

PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 60f, -60f, 40f, -40f, -20f, 20f, 10f, -10f, 0f);  
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xffffffff, 0xffff00ff, 0xffffff00, 0xffffffff);  
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, colorHolder);  
animator.setDuration(3000);  
animator.setInterpolator(new AccelerateInterpolator());  
animator.start();  

给一个ObjectAnimator设置了两个属性动画

KeyFrame

KeyFrame就是关键帧,从电影动画的原理上来说,关键帧就是整个动画过程中,某个特定时刻的画面图像。那么,将这个理解放到属性动画这里,关键帧就是某个特定阶段点的属性值。这里说的阶段点,是指动画完成的进度点。先来看看它的实例化函数

public static Keyframe ofFloat(float fraction, float value) 

其中第一个参数fraction,就是进度或者说比例,简单的理解就是动画进行到多少进度时,对应的属性值是多少。
KeyFrame的简单用法如下

Keyframe frame0 = Keyframe.ofFloat(0f, 0);  
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);  
Keyframe frame2 = Keyframe.ofFloat(1, 0);  
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,frame1,frame2);  
 Animator animator = ObjectAnimator.ofPropertyValuesHolder(mImage,frameHolder);  
animator.setDuration(1000);  
animator.start(); 

需要说明的是,给PropertyValuesHolder设置KeyFrame对象不能少于两个,只有一个KeyFrame形成不了动画。默认情况下,如代码中的frame0到frame1这段动画中,属性值的变化是匀速的,frame1到frame2这段动画中,属性值的变化也是匀速的。但是这两个速率是不相等的,明显前一段的速率要大一些,后一段的速率要小一些。

下面的小例子,演示了属性动画为ViewGroup的子View的显示和隐藏设置过渡动画:

llImageView = (LinearLayout) root.findViewById(R.id.ll_image);

LayoutTransition transition = new LayoutTransition();

transition.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_APPEARING, 0);

ObjectAnimator appearingAnimator = ObjectAnimator
        .ofPropertyValuesHolder(
                (Object) null,
                PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f),
                PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f),
                PropertyValuesHolder.ofFloat("alpha", 0, 1.0f));
transition.setAnimator(LayoutTransition.APPEARING, appearingAnimator);
transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING));
transition.setStartDelay(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));

ObjectAnimator disappearingAnimator = ObjectAnimator
        .ofPropertyValuesHolder(
                (Object) null,
                PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f),
                PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f),
                PropertyValuesHolder.ofFloat("alpha", 1.0f, 0));
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnimator);
transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
transition.setStartDelay(LayoutTransition.DISAPPEARING, 0);

transition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));

llImageView.setLayoutTransition(transition);

顺带解释下过渡动画的四种类型CHANGE_APPEARING、APPEARING、DISAPPEARING和CHANGE_DISAPPEARING

APPEARING
当通过 设置子View的可见性为VISIBLE或者通过addView方法添加子View 来显示子View时,
子View就会执行该类型的动画。
该类型动画的周期为300毫秒,默认延迟为300毫秒。
DISAPPEARING
当通过 设置子View的可见性为GONE或者通过removeView方法移除子View 来隐藏子View时,
子View就会执行该类型的动画。
该类型动画的周期为300毫秒,默认延迟为0毫秒。
CHANGE_APPEARING
当显示子View时,所有的兄弟View就会立即依次执行该类型动画并且兄弟View之间执行动画的间隙默认为0毫秒,然后才会执行显示子View的动画。
该类型动画的周期为300毫秒,默认延迟为0毫秒。
CHANGE_DISAPPEARING
当隐藏子View的动画执行完毕后,所有的兄弟View就会依次执行该类型动画并且兄弟View之间执行动画的间隙默认为0毫秒。
该类型动画的周期为300毫秒,默认延迟为300毫秒。

设置监听器

给属性动画设置监听器,ViewPropertyAnimator和ObjectAnimator略微不一样;ViewPropertyAnimator用的是setListner和setUpdateListner方法,可以设置一个监听器,移除监听器时,通过setListner(null)和setUpdateListner(null),来移除;而ObjetcAnimator用的是addListner和addUpdateListner方法,可以添加一个或多个监听器,移除监听器则是通过removeListner(listner)和removeUpdateListner(listner)来移除指定的监听器对象。
由于ObjectAnimator支持用pause()方法暂停动画,所以多了一对addPauseListner和removePauseListner方法的支持;而ViewPropertyAnimator独有的withStartAction(action)和withEndAction(action),可以为其设置一次性的针对动画开始和结束的处理。

ViewPropertyAnimator.setListener() / ObjectAnimator.addListener()

它们的参数类型都是Animator.AnimatorListener,有4个回调方法:

@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}

其中值得注意的是:就算动画未执行完被取消,onAnimationEnd()也会被回调。也就是说动画被取消时,会先回调onAnimationCancel(),再回调onAnimationEnd()。
由于ViewPropertyAnimator不支持重复执行,所以ViewPropertyAnimator的监听器中的onAnimatorRepeat()方法相当于无效。

ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()

它们的参数类型都是Animator.AnimatorUpdateListener。回调方法只有一个

@Override
public void onAnimationUpdate(ValueAnimator animation) {}

当动画的属性更新时(不严谨的说,即每过 10 毫秒,动画的完成度更新时),这个方法被调用。
方法的参数是一个 ValueAnimator,ValueAnimator 是 ObjectAnimator 的父类,也是 ViewPropertyAnimator 的内部实现,所以这个参数其实就是 ViewPropertyAnimator 内部的那个 ValueAnimator,或者对于 ObjectAnimator 来说就是它自己本身。

ObjectAnimator.addPauseListener()

参数类型是Animator.AnimatorPauseListener,有两个回调方法

@Override
public void onAnimationPause(Animator animation) {}
@Override
public void onAnimationResume(Animator animation) {}
ViewPropertyAnimator.withStartAction/EndAction()

这两个方法是 ViewPropertyAnimator 的独有方法。它们和 set/addListener() 中回调的 onAnimationStart() / onAnimationEnd() 相比起来的不同主要有两点:

withStartAction() / withEndAction() 是一次性的(只会执行一次),在动画执行结束后就自动弃掉了,就算之后再重用 ViewPropertyAnimator 来做别的动画,用它们设置的回调也不会再被调用。而 set/addListener() 所设置的 AnimatorListener 是持续有效的,当动画重复执行时,回调总会被调用。

withEndAction() 设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和 AnimatorListener.onAnimationEnd() 的行为是不一致的。

本文参考:
https://blog.csdn.net/singwhatiwanna/article/details/17841165
https://www.jianshu.com/p/b117c974deaf
https://juejin.im/post/5b5ac6eef265da0f6f1aad86
https://hencoder.com/ui-1-6/

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

推荐阅读更多精彩内容

  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 5,958评论 1 38
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,580评论 0 9
  • 属性动画 属性动画出现的原因 属性动画(Property Animation)是在 Android 3.0(API...
    luoqiang108阅读 1,097评论 0 1
  • 动画基础概念 动画分类 Android 中动画分为两种,一种是 Tween 动画、还有一种是 Frame 动画。 ...
    Rtia阅读 1,142评论 0 6
  • 卧听海涛颂,渔灯三五更。 裙裾祺福舞,篝火彻天明。 月下无花趣,兴阑有酒酲。 子陵垂钓志,狂浪寄浮生。
    月圆天心阅读 165评论 0 2