本文转载自微信公众号「Android开发编程」,作者Android开发编程 。转载本文请联系Android开发编程公众号。
前言
动画的使用是 Android 开发中常用的知识
可是动画的种类繁多、使用复杂,每当需要采用自定义动画 实现 复杂的动画效果时,很多开发者就显得束手无策;
今天我们就来从源码中分析属性动画原理
一、动画简单应用
ValueAnimator
属性动画的最核心的类,原理:控制值的变化,之后手动赋值给对象的属性,从而实现动画;
对于控制的值的不同,Android 提供给我们三种构造方法来实例ValueAnimator对象:
ValueAnimator.ofInt(int... values) -- 整型数值
ValueAnimator.ofFloat(float... values) -- 浮点型数值
ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型
1、java方式
- //设置动画 始 & 末值
- //ofInt()两个作用:
- //1. 获取实例
- //2. 在传入参数之间平滑过渡
- //如下则0平滑过渡到3
- ValueAnimator animator = ValueAnimator.ofInt(0,3);
- //如下传入多个参数,效果则为0->5,5->3,3->10
- //ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);
- //设置动画的基础属性
- animator.setDuration(5000);//播放时长
- animator.setStartDelay(300);//延迟播放
- animator.setRepeatCount(0);//重放次数
- animator.setRepeatMode(ValueAnimator.RESTART);
- //重放模式
- //ValueAnimator.START:正序
- //ValueAnimator.REVERSE:倒序
- //设置更新监听
- //值 改变一次,该方法就执行一次
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- //获取改变后的值
- int currentValue = (int) animation.getAnimatedValue();
- //输出改变后的值
- Log.d("test", "onAnimationUpdate: " + currentValue);
- //改变后的值发赋值给对象的属性值
- view.setproperty(currentValue);
- //刷新视图
- view.requestLayout();
- }
- });
- //启动动画
- animator.start();
2、 XML 方式
在路径 res/animator/ 路径下常见 XML 文件,如 set_animator.xml
在上述文件中设置动画参数
- // ValueAnimator采用<animator> 标签
- <animator xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="1000"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType"
- android:repeatCount="1"
- android:repeatMode="reverse"/>
- />
Java代码启动动画
- Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
- // 载入XML动画
- animator.setTarget(view);
- // 设置动画对象
- animator.start();
二、原理详解
1、创建动画
- ObjectAnimator.ofFloat()开始;
- /**
- * 构建一个返回值为 float 的 ObjectAnimator 的实例
- *
- * @param target 作用于动画的对象。
- * @param propertyName 属性名称,要求对象须有setXXX() 方法,且是 public 的。
- * @param values,属性变化的值,可以设置 1 个或者 多个。当只有 1 个时,起始值为属性值本身。当有 2 个值时,第 1 个为起始值,第 2 个为终止值。当超过 2 个时,首尾值的定义与 2 个时一样,中间值做需要经过的值。
- */
- public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
- ObjectAnimator anim = new ObjectAnimator(target, propertyName);
- anim.setFloatValues(values);
- return anim;
- }
- 创建一个 ObjectAnimator 的实例,然后为该实例设置 values;
- 那么,继续看 ObjectAnimator 的构建;
构造 ObjectaAnimator
- private ObjectAnimator(Object target, String propertyName) {
- setTarget(target);
- setPropertyName(propertyName);
- }
分别调用了 setTarget() 方法和setPropertyName();
2、setTarget()
- public void setTarget(@Nullable Object target) {
- final Object oldTarget = getTarget();
- if (oldTarget != target) {
- if (isStarted()) {
- cancel();
- }
- mTarget = target == null ? null : new WeakReference<Object>(target);
- // New target should cause re-initialization prior to starting
- mInitialized = false;
- }
- }
存在旧动画对象(也可为 null) 与新设置的动画对象不一致;
如果旧动画是开始了的状态,则先取消动画,然后将动画对象以弱引用对象为记录下来;
3、setPropertyName()
- public void setPropertyName(@NonNull String propertyName) {
- // mValues could be null if this is being constructed piecemeal. Just record the
- // propertyName to be used later when setValues() is called if so.
- if (mValues != null) {
- PropertyValuesHolder valuesHolder = mValues[0];
- String oldName = valuesHolder.getPropertyName();
- valuesHolder.setPropertyName(propertyName);
- mValuesMap.remove(oldName);
- mValuesMap.put(propertyName, valuesHolder);
- }
- mPropertyName = propertyName;
- // New property/values/target should cause re-initialization prior to starting
- mInitialized = false;
- }
- 记录下 propertyName 的名字;
- 而如果已经有这个 propertyName,则会替换其相应的 PropertyValuesHolder,这里用了一个 HashMap 来保存 propertyName 和 PropertyValuesHolder
- 如果propertyName 是 "translationX";
- 接下来看 setFloatValues() 方法;
4、setFloatValues()
- @Override
- public void setFloatValues(float... values) {
- if (mValues == null || mValues.length == 0) {
- // 当前还没有任何值
- if (mProperty != null) {
- setValues(PropertyValuesHolder.ofFloat(mProperty, values));
- } else {
- setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
- }
- } else {
- // 当前已经有值的情况,调用父类的 setFloatValues()
- super.setFloatValues(values);
- }
- }
父类,即 ValueAnimator ,其方法setFloatValues() 如下;
5、ValueAnimator#setFloatValues()
- public void setFloatValues(float... values) {
- if (values == null || values.length == 0) {
- return;
- }
- if (mValues == null || mValues.length == 0) {
- setValues(PropertyValuesHolder.ofFloat("", values));
- } else {
- PropertyValuesHolder valuesHolder = mValues[0];
- valuesHolder.setFloatValues(values);
- }
- // New property/values/target should cause re-initialization prior to starting
- mInitialized = false;
- }
- 不管是否调用父类的 setFloatValues();
- 最后都是要将 values 逐个构造成 PropertyValuesHolder,最后存放在前面所说的 HashMap 里面;
- 当然,如果这里的 hashMap 还没有初始化,则先会将其初始化;
- 最关键的是要构建出 PropertyValuesHolder 这个对象;
- 那么就继续来看看 PropertyValuesHolder#ofFloat() 方法;
6、PropertyValuesHolder#ofFloat()
- public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
- return new FloatPropertyValuesHolder(propertyName, values);
- }
- 构造 FloatPropertyValuesHolder;
- FloatPropertyValuesHolder
- public FloatPropertyValuesHolder(String propertyName, float... values) {
- super(propertyName);
- setFloatValues(values);
- }
- FloatPropertyValuesHolder 构造函数比较简单,调用父类的构造方法并传递了 propertyName;
- 关键是进一步 setFloatValues() 方法的调用;
- 其又进一步调用了父类的 setFloatValues(),在父类的 setFloatValues() 方法里初始化了动画的关键帧;
- PropertyValuesHolder#setFloatValues()
- public void setFloatValues(float... values) {
- mValueType = float.class;
- mKeyframes = KeyframeSet.ofFloat(values);
- }
- 进一步调用了 KeyframeSet#ofFloat() 方法以完成关键帧的构造;
- KeyframeSet 是接口 Keyframe 的实现类;
7、KeyframeSet#ofFloat()
- public static KeyframeSet ofFloat(float... values) {
- boolean badValue = false;
- int numKeyframes = values.length;
- // 至少要 2 帧
- FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
- // 然后构造出每一帧,每一帧中主要有 2 个重要的参数 fraction 以及 value
- if (numKeyframes == 1) {
- keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
- keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
- if (Float.isNaN(values[0])) {
- badValue = true;
- }
- } else {
- keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
- for (int i = 1; i < numKeyframes; ++i) {
- keyframes[i] =
- (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
- if (Float.isNaN(values[i])) {
- badValue = true;
- }
- }
- }
- if (badValue) {
- Log.w("Animator", "Bad value (NaN) in float animator");
- }
- // 最后将所有的 关键帧 汇集到一个集合中
- return new FloatKeyframeSet(keyframes);
- }
其主要内容是:
- 构造动画的关键帧,且动画里至少要有 2 个关键帧;
- 关键帧中有 2 个重要的参数,fraction这个可以看成是关键帧的序号,value 关键帧的值,可能是起始值,也可能是中间的某个值;
- 最后将关键帧汇集成一个关键帧集返回给 PropertyValuesHolder;
8、setDuration()
- @Override
- @NonNull
- public ObjectAnimator setDuration(long duration) {
- super.setDuration(duration);
- return this;
- }
调用了父类 ValueAnimator 的 setDuration();
- ValueAnimator#setDuration()
- @Override
- public ValueAnimator setDuration(long duration) {
- if (duration < 0) {
- throw new IllegalArgumentException("Animators cannot have negative duration: " +
- duration);
- }
- mDuration = duration;
- return this;
- }
setDuration() 只是简单的存储下 duration 的值,仅此而已,那么继续分析 setInterpolator();
9、setInterpolator()
- @Override
- public void setInterpolator(TimeInterpolator value) {
- if (value != null) {
- mInterpolator = value;
- } else {
- mInterpolator = new LinearInterpolator();
- }
- }
传递的是 null 的话,则默认使用的便是 LinearInterpolator,即线性插值器;
我们这里的假设的场景也是设置了 LinearInterpolator,这是最简单的插值器,其作用就是完成匀速运动;
10、 LinearInterpolator粗略分析;
- /**
- * 插值器定义了动画变化的频率,其可以是线性的也可以是非线性的,如加速运动或者减速运动;
- */
- public interface TimeInterpolator {
- /**
- * 这里传进来的 input 代表当前时间与总时间的比,根据这个时间占比返回当前的变化频率。其输出与输值都在 [0,1] 之间
- */
- float getInterpolation(float input);
- }
插值器的关键定义便是实现 getInterpolation() 方法,即根据当前动画运行的时间占比来计算当前动画的变化频率;
那么来看看 LinearInterpolator 的 getInterpolation() 实现;
- LinearInterpolator#getInterpolation()
- public float getInterpolation(float input) {
- return input;
- }
对,就是返回原值,因为时间的变化肯定始终都是匀速的;
11、start
启动动画从 start() 方法开始
- @Override
- public void start() {
- AnimationHandler.getInstance().autoCancelBasedOn(this);
- if (DBG) {
- Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
- for (int i = 0; i < mValues.length; ++i) {
- PropertyValuesHolder pvh = mValues[i];
- Log.d(LOG_TAG, " Values[" + i + "]: " +
- pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
- pvh.mKeyframes.getValue(1));
- }
- }
- super.start();
- }
- 先确认动画已经取消;这个方法里的重要的那句代码就是调用父类 ValueAnimator 的 start();
- 父类对外的 start() 方法很简单,其主要的实现在另一个重载的私有 start() 方法上;
- private void start(boolean playBackwards) {
- .....
- mReversing = playBackwards;
- // 重置脉冲为 "true"
- mSelfPulse = !mSuppressSelfPulseRequested;
- .....
- // 添加脉冲回调用
- 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);
- }
- }
- }
其中之一是 addAnimationCallback(),其主要是向 AnimationHander 添加一个回调接口AnimationHandler.AnimationFrameCallback,如下代码;
- /**
- * Register to get a callback on the next frame after the delay.
- */
- public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
- if (mAnimationCallbacks.size() == 0) {
- getProvider().postFrameCallback(mFrameCallback);
- }
- if (!mAnimationCallbacks.contains(callback)) {
- mAnimationCallbacks.add(callback);
- }
- if (delay > 0) {
- mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
- }
- }
- ValueAnimator 就实现了 AnimationFrameCallback,所以这里添加的是 ValueAnimator 的实例;
- 最终被添加到 mAnimationCallbacks 这个队列中;
12、startAnimation()
- private void startAnimation() {
- ......
- mAnimationEndRequested = false;
- initAnimation();
- mRunning = true;
- if (mSeekFraction >= 0) {
- mOverallFraction = mSeekFraction;
- } else {
- mOverallFraction = 0f;
- }
- if (mListeners != null) {
- // 通过动画监听器动画开始了
- notifyStartListeners();
- }
- }
关键调用 initAnimation()
- void initAnimation() {
- if (!mInitialized) {
- int numValues = mValues.length;
- for (int i = 0; i < numValues; ++i) {
- mValues[i].init();
- }
- mInitialized = true;
- }
- }
mValues 是 PropertyValuesHolder 数组,这里的目的是初始化 PropertyValuesHolder;
- void init() {
- if (mEvaluator == null) {
- // We already handle int and float automatically, but not their Object
- // equivalents
- mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
- (mValueType == Float.class) ? sFloatEvaluator :
- null;
- }
- if (mEvaluator != null) {
- // KeyframeSet knows how to evaluate the common types - only give it a custom
- // evaluator if one has been set on this class
- mKeyframes.setEvaluator(mEvaluator);
- }
- }
- init() 方法的主要目的是就是给关键帧设置估值器;
- 前面调用的是 ObjectAnimator#ofFloat() 方法,所以这里默认给的就是 FloatEvaluator;
- 接下来就会进一步调用 setCurrentPlayTime() 来开始动画;
13、setCurrentPlayTime()
- public void setCurrentPlayTime(long playTime) {
- float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
- setCurrentFraction(fraction);
- }
- 初始时调用的是setCurrentPlayTime(0),也就是 playTime 为 0,而 mDuration 就是我们自己通过 setDuration() 来设置的;
- 所以这里得到的 fraction 也是 0;
- 进一步看 setCurrentFraction() 方法;
14、setCurrentFraction
- public void setCurrentFraction(float fraction) {
- // 再次调用 initAnimation() ,前面初始化过了,所以这里是无用的
- initAnimation();
- // 校准 fraction 为 [0, mRepeatCount + 1]
- fraction = clampFraction(fraction);
- mStartTimeCommitted = true; // do not allow start time to be compensated for jank
- if (isPulsingInternal()) {
- // 随机时间?
- long seekTime = (long) (getScaledDuration() * fraction);
- // 获取动画的当前运行时间
- long currentTime = AnimationUtils.currentAnimationTimeMillis();
- // Only modify the start time when the animation is running. Seek fraction will ensure
- // non-running animations skip to the correct start time.
- // 得到开始时间
- mStartTime = currentTime - seekTime;
- } else {
- // If the animation loop hasn't started, or during start delay, the startTime will be
- // adjusted once the delay has passed based on seek fraction.
- mSeekFraction = fraction;
- }
- mOverallFraction = fraction;
- final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
- // 执行动画,注意这里会先调用子类的 animateValue() 方法
- animateValue(currentIterationFraction);
- }
前面都是一些时间的计算,得到当前真正的currentIterationFraction,最后会通过调用animateValue() 来执行动画;
15、 ObjectAnimator#animateValue()
- void animateValue(float fraction) {
- final Object target = getTarget();
- if (mTarget != null && target == null) {
- // We lost the target reference, cancel and clean up. Note: we allow null target if the
- /// target has never been set.
- cancel();
- return;
- }
- // 调用父类的 animateValue() ,这个很关键,时间插值与估值器的计算都在父类的 animateValue() 方法中进行的。
- super.animateValue(fraction);
- int numValues = mValues.length;
- for (int i = 0; i < numValues; ++i) {
- // 这里的 mValues 的是PropertyValuesHolder[],也就是在 PropertyValuesHolder 里面来改变了目标 target 的属性值。
- mValues[i].setAnimatedValue(target);
- }
- }
父类 ValueAnimator#animateValue()
- void animateValue(float fraction) {
- // 获取时间插值
- fraction = mInterpolator.getInterpolation(fraction);
- mCurrentFraction = fraction;
- int numValues = mValues.length;
- // 将时间插值送给估值器,计算出 values
- for (int i = 0; i < numValues; ++i) {
- mValues[i].calculateValue(fraction);
- }
- // 发出通知
- if (mUpdateListeners != null) {
- int numListeners = mUpdateListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- mUpdateListeners.get(i).onAnimationUpdate(this);
- }
- }
- }
animateValue(): 计算时间插值和估值器、调用 PropertyValuesHolder 来改变属性;
- void setAnimatedValue(Object target) {
- if (mProperty != null) {
- mProperty.set(target, getAnimatedValue());
- }
- if (mSetter != null) {
- try {
- mTmpValueArray[0] = getAnimatedValue();
- // 通过反射调用来修改属性值
- mSetter.invoke(target, mTmpValueArray);
- } catch (InvocationTargetException e) {
- Log.e("PropertyValuesHolder", e.toString());
- } catch (IllegalAccessException e) {
- Log.e("PropertyValuesHolder", e.toString());
- }
- }
- }
- 这里就是通过属性的 Setter 方法来修改属性的;
- 分析到这里,就完成了动画的一帧关键帧的执行;
- 剩下的帧是怎么驱动的呢?还是得回到 start() 方法里面,在这里最初分析到 addAnimationFrameCallback() 方法;
- 这个方法里等于是向AnimationHandler注册了AnimationHandler.AnimationFrameCallback;
- 这个 callback 中其中之一的方法是 doAnimationFrame();
在 ValueAnimator 的实现中如下;
- public final boolean doAnimationFrame(long frameTime) {
- .....
- boolean finished = animateBasedOnTime(currentTime);
- if (finished) {
- endAnimation();
- }
- return finished;
- }
这段代码原来也是很长的,我们只看关键调用 animateBasedOnTime()
- boolean animateBasedOnTime(long currentTime) {
- boolean done = false;
- if (mRunning) {
- .....
- float currentIterationFraction = getCurrentIterationFraction(
- mOverallFraction, mReversing);
- animateValue(currentIterationFraction);
- }
- return done;
- }
- 目的也还是计算出 currentIterationFraction;
- 通过 animateValue() 方法来执行动画;
- 可以看到只要 doAnimationFrame() 被不断的调用,就会产生动画的一个关键帧;
- 如果关键帧是连续的,那么最后也就产生了我们所看到的动画;
- 再来分析doAnimationFrame() 是如何被不断调用的;
- 这个需要回到 AnimationHandler 中来,在 AnimationHandler 中有一个非常重要的 callback 实现——Choreographer.FrameCallback;
- private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
- @Override
- public void doFrame(long frameTimeNanos) {
- doAnimationFrame(getProvider().getFrameTime());
- if (mAnimationCallbacks.size() > 0) {
- getProvider().postFrameCallback(this);
- }
- }
- };
- Andorid 中的重绘就是由Choreographer在 1 秒内产生 60 个 vsync 来通知 view tree 进行 view 的重绘的;
- 而 vsync 产生后会调用它的监听者回调接口 Choreographer.FrameCallback,;
- 也就是说,只要向Choreographer注册了这个接口,就会每 1 秒里收到 60 次回调;
- 因此,在这里就实现了不断地调用 doAnimationFrame() 来驱动动画了;
16、流程总结
- 动画是由许多的关键帧组成的;
- 属性动画的主要组成是 PropertyValuesHolder,而 PropertyValuesHolder 又封装了关键帧;
- 动画开始后,其监听了 Choreographer 的 vsync,使得其可以不断地调用 doAnimationFrame() 来驱动动画执行每一个关键帧;
- 每一次的 doAnimationFrame() 调用都会去计算时间插值,而通过时间插值器计算得到 fraction 又会传给估值器,使得估值器可以计算出属性的当前值;
- 最后再通过 PropertyValuesHolder 所记录下的 Setter 方法,以反射的方式来修改目标属性的值;
- 当属性值一帧一帧的改变后,形成连续后,便是我们所见到的动画;
总结
2021年最后一个月,大家一起加油努力;