Android动画系列——属性动画

作者: zackyG | 来源:发表于2019-01-15 22:42 被阅读94次

    属性动画(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/

    相关文章

      网友评论

        本文标题:Android动画系列——属性动画

        本文链接:https://www.haomeiwen.com/subject/dditrqtx.html