Android开发Android开发Android技术知识

Android中的基础动画 属性动画(Property Anim

2019-11-24  本文已影响0人  彭空空

导读

Android中的基础动画 属性动画(Property Animation)

通过前面的文章Android中的视图动画(View Animation)(View动画、补间动画)我们知道视图动画只是改变了View的视觉效果,而实际并未变更,而属性动画可谓是视图动画的加强版,并且具有更好的特性,因为属性动画不仅改变了视觉效果,而且实际也跟随变动了,并保留了视图动画如监听等功能。

举个不恰当的例子:
彭空空做梦赚了一卡车的人民币,实际收入呢,0元;
马云大佬做梦赚了一卡车的人民币,实际收入一卡车的人民币。

这里我写了两段简单的代码,一个是属性动画,一个是补间动画,效果均是让Button平移的效果,并对两个Button设置了点击事件,以下是关键代码:

private void showTweenAnim() {
        Animation translateAnimation = new TranslateAnimation(0, 800, 0, 0);
        translateAnimation.setDuration(3000);
        translateAnimation.setRepeatCount(-1);
        button2.startAnimation(translateAnimation);
    }
private void showObjectAnimatorOfFloat() {
        ObjectAnimator animator = ObjectAnimator.ofFloat(button1, "translationX", 0, 500);
        animator.setDuration(3000);
        animator.setRepeatCount(-1);
        animator.start();
    }
属性动画和补间动画

这是以上代码的动画效果和点击事件的响应,可以看到Button从左边移动到右边,属性动画一直可以响应点击事件,而补间动画只有在原来的位置才响应事件。

再看两个动画的代码,对比发现,除了构造(静态方法)基本上一致,而我们知道补间动画要实现平移、选择、缩放、透明度的动画,分别需要TranslateAnimation(平移动画)、RotateAnimation(旋转动画)、ScaleAnimation(缩放动画)、AlphaAnimation(透明度动画)这些类,而上面的代码中,属性动画使用了ObjectAnimator的ofFloat()方法,后面传入关键参数“translationX”,就实现了平移动画,看来属性动画内部进行了扩展性的封装,这里就不去具体研究是如何封装的了。

下面具体来看看ObjectAnimator.ofFloat()方法:

 public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setIntValues(values);
        return anim;
    }
   /**
    * This class holds information about a property and the values that that property
    * should take on during an animation. PropertyValuesHolder objects can be used to create
    * animations with ValueAnimator or ObjectAnimator that operate on several different properties
    * in parallel.
    */
   public class PropertyValuesHolder implements Cloneable {...}

通过注释,我们得知PropertyValuesHolder是用来封装属性相关的变量:


PropertyValuesHolder的相关变量

到这里,准备工作就做完了,即为target准备propertyName的动画,把传递过来的values转换为系统识别的开始帧、结束帧。


setDuration()、setRepeatCount()、就不展开了,主要看看start()方法:

    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {...}
        super.start();
    }

新出现一个以Handler命名的类AnimationHandler,由于后面是getInstance()方法,我们大胆猜测这是一个单例,单列模式是编程中常见的设计模式,可查看单例的相关知识。跟着后面的autoCancelBasedOn()顾明思意应该就是用于动画取消,保证即将执行的动画的唯一性,这里也不展开了,先看看AnimationHandler的定义:

  /**
   * This custom, static handler handles the timing pulse that is shared by all active
   * ValueAnimators. This approach ensures that the setting of animation values will happen on the
   * same thread that animations start on, and that all animations will share the same times for
   * calculating their values, which makes synchronizing animations possible.
   *
   * The handler uses the Choreographer by default for doing periodic callbacks. A custom
   * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
   * may be independent of UI frame update. This could be useful in testing.
   *
   * @hide
   */
  public class AnimationHandler {}

大概意思是说AnimationHandler主要是用于处理所有活动的属性动画共享的“时间脉冲”,这个时间脉冲即从开始到结束每个时间段的“值”,AnimationHandler保证了一个动画的完整播放都是发生在同一个线程,该处理程序默认情况下使用Choreographer进行定期回调。 可以在处理程序上设置自定义AnimationFrameCallbackProvider,以提供可能独立于UI框架更新的定时脉冲。由于该类非常重要,所以后面还会涉及到该类下的其他方法。

知道了AnimationHandler具有重要的管理的作用后,继续追踪父类ValueAnimator的start()方法:

   private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        ...
        addAnimationCallback(0);
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

在start()方法中关注这些:

1、以Debug模式运行
2、在start()方法上打上断点
3、在动画中添加addUpdateListener监听,并打上断点
4、在AnimationHandler类下的mFrameCallback中打上断点
5、点击执行动画
6、通过点击resume跳到下一个断点,


调试1.png
调试2.png

这里需要注意 doAnimationFrame 的断点,必须要在后面打上,而不是一开始打上
通过debug我们会发现,doAnimationFrame之后就会调用addUpdateListener,然后重复如此,一直到动画结束,并且越简单的动画,重复次数越少,反之则重复次数越多,这里可以通过setDuration(30)和setDuration(3000)进行对比。
整个流程就是:通过getAnimationHandler().addAnimationFrameCallback(this, delay)进行回调绑定,这个回调就是父类ValueAnimator类实现的AnimationHandler类中的AnimationFrameCallback回调,AnimationFrameCallback中的方法doAnimationFrame()在Choreographer类的FrameCallback回调中的方法doFrame()中被执行。

到这里的结论就是Choreographer通过调用doAnimationFrame()来驱动动画执行每一个关键帧。

在ObjectAnimator类的animateValue()方法中,需要注意

至此,动画的第一帧就执行完毕了。


我们通过ObjectAnimator.ofFloat()方法,查看了跟踪查看了整个属性动画的机制。这里贴出动画机制的相关方法,由于简书的这个图片压缩的太狠,最后只有这张图勉强能看清(建议右键,在新页面打开图片):


ValueAnimator.png

我们得出一些结论:

当然,从简单的角度来说动画机制就是如此这般了,但是这只是粗颗粒而言,上文中海油很多细节并没有展开,比如如何保证动画唯一性、动画的相关时间是如何计算的、比如插值器和估值器是怎么工作的、Choreographer又是如何不断调用的等等问题,后续我会根据时间情况慢慢梳理出来。

上一篇下一篇

猜你喜欢

热点阅读