写在开始
转载请注明: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()获取引用。
上面说的比较拗口,总结起来就是:
- 这个类操作View对象的。
- 提供链式调用设置多个属性动画,这些动画同时进行的。
- 更好的性能,多个属性动画是一次同时变化,只执行一次UI刷新。
- 每个属性提供两种类型方法设置。
- 这个类只能通过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使用很简单,那么来看看它是怎么实现的。
内部类
- NameValuesHolder 标记每个属性值对应的开始值和变化值。
- PropertyBundle 这个类包含的全局动画的属性集合的信息。包含一个NameValueHolder列表和mPropertyMask,mPropertyMask是标记这个动画都有什么属性变化。通过这个类我们可以知道都有那么属性需要变化,变化的值是什么。
- AnimatorEventListener 操作各种Animator事件的工具类。这个类实现了Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener接口。我们只需要关心这些事件的结束事件(在动画结束之后用来清除animator map的事件)和更新事件(计算view对象的当前属性值,下面还会介绍)。
重要属性
ArrayList mPendingAnimations : 要进行动画的属性值(NameValueHolder)列表。
Runnable mAnimationStarter : 用来执行动画的Runnable。它会执行startAnimation方法,启动动画。
HashMap mAnimatorMap : Animator到PropertyBundle的Map。这个Map放到是正在执行的动画和对应的PropertyBundle,这个PropertyBundle包含这这次动画的所有进行动画的属性的信息。在AnimatorEventListener的更新方法中会通过这个map获取相应的属性,然后更新属性值,达到动画效果。这个Map同时还保证当前动画的属性变化是唯一的,不会存在两个Animator在操作同一个属性。
重要方法
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); } |
这个方法主要完成三件事:
-
如果当前已经存在正在运行的Animator的话,并且这个Animator包含这个属性的话,将这个Animator的这个属性移除,移除完之后如果这个Animator没有属性了就结束它。这个操作是保证View对象的一个属性只有一个动画在操作它。
-
将属性和属性值变化装到NameValuesHolder类中,然后放到mPendingAnimations列表中。以便在之后放到mAnimatorMap中。
-
取消并且重新启动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方法主要做了这些事情:
- 创建简单的Animator,变化值从0到1,设置监听器mAnimatorEventListener。
- 拷贝mPendingAnimations这个列表,并计算属性值标记propertyMask,生成PropertyBundle对象。
- 使用mAnimatorMap保存这个Animator和对应的PropertyBundle对象。以便在animatePropertyBy方法和Animator监听器mAnimatorEventListener中使用。
- 设置监听器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类的使用串起来了:
- 通过imageView.animate()获取ViewPropertyAnimator对象。
- 调用alpha、rotationY等方法,返回当前ViewPropertyAnimator对象,可以继续调用。
- alpha等方法会调用animatePropertyBy(int constantName, float startValue, float byValue)方法。
- animatePropertyBy方法启动mAnimationStarter,调用startAnimation,开始动画。
- 在动画的监听器里面设置所有属性的变化值,并刷新。