属性动画系列之ViewPropertyAnimator

写在开始

转载请注明:http://www.qinglinyi.com/posts/ViewPropertyAnimator/

属性动画动画系列从开始到现在,其他类例如ValueAnimator、ObjectAnimator还有AnimatorSet等的介绍挺多的,但是关于ViewPropertyAnimator这个类的介绍似乎比较少,所以本文详细的介绍一下这个类(主要是这个类的内部实现)。

类概述

ViewPropertyAnimator这个类能够对View对象进行自动添加和优化属性动画(用来便捷方式处理一系列View对象的属性动画的类)。

这个类提供了链式调用进行多个属性同时变化的动画,更加简洁方便我们操作组合动画(多个属性同时进行变化)。

这个类可以为同时动画提供更好的性能。这个类在动画进行的时候只对多个属性的变化进行一次invalidate调用,而不是对变化每个属性进行调用(n个ObjectAnimator就会进行n次属性变化,就有n次invalidate)。当然这个类的调用方式更方便,调用对应属性方法传一个属性值就可以自动实现动画。每个属性方法都有两种调用形式,例如 alpha(float value) 和alphaBy(float value),前者是变化到多少,后者是变化多少。

这个类没用提供公开的构造方法,通过调用view的animate()获取引用。

上面说的比较拗口,总结起来就是:

  1. 这个类操作View对象的。
  2. 提供链式调用设置多个属性动画,这些动画同时进行的。
  3. 更好的性能,多个属性动画是一次同时变化,只执行一次UI刷新。
  4. 每个属性提供两种类型方法设置。
  5. 这个类只能通过View的animate()获取引用进行通话设置。

使用

1
2
3
4
5
imageView.animate()
          .setDuration(4000)
          .rotationY(45f)
          .translationX(imageView.getWidth())
          .alpha(0f);

如上,使用很方便,对imageView设置三个属性的变化,同时进行动画。

等价的ObjectAnimator形式是这样的:

1
2
3
4
5
new AnimatorSet().playTogether(
      ObjectAnimator.ofFloat(imageView, "translationX", imageView.getWidth()),
      ObjectAnimator.ofFloat(imageView, "alpha", 0),
      ObjectAnimator.ofFloat(imageView, "rotationY", 45f)
);

前者明显更简洁

ViewPropertyAnimator使用很简单,那么来看看它是怎么实现的。

内部类

  1. NameValuesHolder 标记每个属性值对应的开始值和变化值。
  2. PropertyBundle 这个类包含的全局动画的属性集合的信息。包含一个NameValueHolder列表和mPropertyMask,mPropertyMask是标记这个动画都有什么属性变化。通过这个类我们可以知道都有那么属性需要变化,变化的值是什么。
  3. AnimatorEventListener 操作各种Animator事件的工具类。这个类实现了Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener接口。我们只需要关心这些事件的结束事件(在动画结束之后用来清除animator map的事件)和更新事件(计算view对象的当前属性值,下面还会介绍)。

重要属性

ArrayList mPendingAnimations : 要进行动画的属性值(NameValueHolder)列表。

mPendingAnimations

Runnable mAnimationStarter : 用来执行动画的Runnable。它会执行startAnimation方法,启动动画。

mAnimationStarter

HashMap mAnimatorMap : Animator到PropertyBundle的Map。这个Map放到是正在执行的动画和对应的PropertyBundle,这个PropertyBundle包含这这次动画的所有进行动画的属性的信息。在AnimatorEventListener的更新方法中会通过这个map获取相应的属性,然后更新属性值,达到动画效果。这个Map同时还保证当前动画的属性变化是唯一的,不会存在两个Animator在操作同一个属性。

mAnimatorMap

重要方法

animatePropertyBy(int constantName, float startValue, float byValue)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 private void animatePropertyBy(int constantName, float startValue, float byValue) {
     // 移除正在进行动画的对应的这个属性
     if (mAnimatorMap.size() > 0) {
         Animator animatorToCancel = null;
         Set<Animator> animatorSet = mAnimatorMap.keySet();
         for (Animator runningAnim : animatorSet) {
             PropertyBundle bundle = mAnimatorMap.get(runningAnim);
             if (bundle.cancel(constantName)) {// 移除对应属性
                 if (bundle.mPropertyMask == NONE) {// 判断还有其他属性没
                     animatorToCancel = runningAnim;
                     break;
                 }
             }
         }
         if (animatorToCancel != null) {
             animatorToCancel.cancel();
         }
     }
// 创建这个属性的NameValuesHolder对象,放到mPendingAnimations列表中
     NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
     mPendingAnimations.add(nameValuePair);
     //  执行动画
     mView.removeCallbacks(mAnimationStarter);
     mView.postOnAnimation(mAnimationStarter);
 }

这个方法主要完成三件事:

  1. 如果当前已经存在正在运行的Animator的话,并且这个Animator包含这个属性的话,将这个Animator的这个属性移除,移除完之后如果这个Animator没有属性了就结束它。这个操作是保证View对象的一个属性只有一个动画在操作它。

  2. 将属性和属性值变化装到NameValuesHolder类中,然后放到mPendingAnimations列表中。以便在之后放到mAnimatorMap中。

  3. 取消并且重新启动mAnimationStarter就是启动动画,并且保证链式设置的属性是同时进行的。

startAnimation方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void startAnimation() {
    if (mRTBackend != null && mRTBackend.startAnimation(this)) {
        return;
    }
    mView.setHasTransientState(true);
    // 创建简单的animator,变化值从0到1
    ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
    // 拷贝mPendingAnimations并且清除原有的mPendingAnimations
    ArrayList<NameValuesHolder> nameValueList =
            (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
    mPendingAnimations.clear();
    // 迭代拷贝之后的mPendingAnimations获取属性值标记propertyMask
    int propertyMask = 0;
    int propertyCount = nameValueList.size();
    for (int i = 0; i < propertyCount; ++i) {
        NameValuesHolder nameValuesHolder = nameValueList.get(i);
        propertyMask |= nameValuesHolder.mNameConstant;
    }
    // 将这个animator放入到mAnimatorMap。
    mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
    ...
    // 设置监听器器
    animator.addUpdateListener(mAnimatorEventListener);
    animator.addListener(mAnimatorEventListener);
    if (mStartDelaySet) {
        animator.setStartDelay(mStartDelay);
    }
    if (mDurationSet) {
        animator.setDuration(mDuration);
    }
    if (mInterpolatorSet) {
        animator.setInterpolator(mInterpolator);
    }
    // 启动动画
    animator.start();
}

在animatePropertyBy方法中执行mView.postOnAnimation(mAnimationStarter)会触发startAnimation方法,startAnimation方法主要做了这些事情:

  1. 创建简单的Animator,变化值从0到1,设置监听器mAnimatorEventListener。
  2. 拷贝mPendingAnimations这个列表,并计算属性值标记propertyMask,生成PropertyBundle对象。
  3. 使用mAnimatorMap保存这个Animator和对应的PropertyBundle对象。以便在animatePropertyBy方法和Animator监听器mAnimatorEventListener中使用。
  4. 设置监听器mAnimatorEventListener并且启动动画。

mAnimatorEventListener的onAnimationUpdate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void onAnimationUpdate(ValueAnimator animation) {
PropertyBundle propertyBundle = mAnimatorMap.get(animation);
    ...
    ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
    if (valueList != null) {
        int count = valueList.size();
        // 迭代所有的属性,计算变化之后的属性值,将属性值设置给对应的属性
        for (int i = 0; i < count; ++i) {
            NameValuesHolder values = valueList.get(i);
            float value = values.mFromValue + fraction * values.mDeltaValue;
            if (values.mNameConstant == ALPHA) {
                alphaHandled = mView.setAlphaNoInvalidation(value);
            } else {
                setValue(values.mNameConstant, value);
            }
        }
    }
    ...
    if (alphaHandled) {
        mView.invalidate(true);
    } else {
        mView.invalidateViewProperty(false, false);
    }
    ...
}

这个方法完成了属性值的设置。通过mAnimatorMap获取对应的所有属性,计算变化之后的属性,然后设置给对应的属性,再进行更行UI。

对照使用串联ViewPropertyAnimator

我们再次回过来看看我们前面是怎么使用的。

1
2
3
4
5
imageView.animate()
          .setDuration(4000)
          .rotationY(45f)
          .translationX(imageView.getWidth())
          .alpha(0f);

通过imageView.animate()获取ViewPropertyAnimator类对象。调用rotationY、translationX、alpha设置属性动画。

我们看看alpha方法的实现

1
2
3
4
5
6
7
8
9
10
public ViewPropertyAnimator alpha(float value) {
    animateProperty(ALPHA, value);
    return this;
}

private void animateProperty(int constantName, float toValue) {
    float fromValue = getValue(constantName);
    float deltaValue = toValue - fromValue;
    animatePropertyBy(constantName, fromValue, deltaValue);
}

最终调用了animatePropertyBy(int constantName, float startValue, float byValue)方法

这样我们就可以将这个ViewPropertyAnimator类的使用串起来了

  1. 通过imageView.animate()获取ViewPropertyAnimator对象。
  2. 调用alpha、rotationY等方法,返回当前ViewPropertyAnimator对象,可以继续调用。
  3. alpha等方法会调用animatePropertyBy(int constantName, float startValue, float byValue)方法。
  4. animatePropertyBy方法启动mAnimationStarter,调用startAnimation,开始动画。
  5. 在动画的监听器里面设置所有属性的变化值,并刷新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值