Android动画-属性动画底层原理浅析
之前发表了三篇文章分别描述了三种动画的使用方式,今天我们来了解一下属性动画的底层原理。
之前的三篇文章链接:
Android动画-视图动画View Animation
Android动画-帧动画Drawable Animation
Android动画-属性动画Property Animation
这样,我们先从代码入口开始,假设我们用的是
ObjectAnimator.ofInt(iv, "width", 500).setDuration(5000).start();
我们进去ofint方法看一看
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setIntValues(values);
return anim;
}
ofint方法里创建了一个ObjectAnimator对象,并向他传入了一个values值,接下来我们看看 ObjectAnimator
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
这里调用了两个方法,我们先看第一个setTarget(target);
@Nullable
public Object getTarget() {
return mTarget == null ? null : mTarget.get();
}
@Override
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;
}
}
这里只是一个动画对象的判断,如果存在旧对象就覆盖,那我们回过头看看另一个方法setPropertyName(propertyName);
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;
}
这里用了HashMap 来保存 propertyName 和 PropertyValuesHolder,这个方法主要还是记录propertyName以及对应的值。那么接下来我们回过头*2看看anim.setIntValues(values);
@Override
public void setIntValues(int... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofInt(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
}
} else {
super.setIntValues(values);
}
}
这里主要是一个values值的判断,继续 super.setIntValues(values);
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
这里也有一个setValues,所以不管父类还是子类,最终还是调用setValues,那我们看看PropertyValuesHolder.ofInt("", values)是什么
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntPropertyValuesHolder(propertyName, values);
}
public IntPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);
setIntValues(values);
}
@Override
public void setIntValues(int... values) {
super.setIntValues(values);
mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
}
这里我就直接连击了,只是一些方法调用,现在看super.setIntValues(values);
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframes = KeyframeSet.ofInt(values);
}
诶,看到了一个熟悉的身影,ofInt,来看看KeyframeSet.ofInt(values);
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
首先,这里new一个IntKeyframe,并且至少长度是2,就是至少要两帧,接下来赋值每一帧,最后合成一个IntKeyframeSet对象返回出去。
ofint的方法到此就先告一段落,简单来讲就是赋值,做一些准备工作,构造出帧,接下来我们回过头*3看看setDuration()
@Override
@NonNull
public ObjectAnimator setDuration(long duration) {
super.setDuration(duration);
return this;
}
@Override
public ValueAnimator setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
mDuration = duration;
return this;
}
啊,这里只是存了一下这个变量,那我们下一个方法,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();
}
这里就个打印,以及确认一下动画有没有已经取消,接下来看super.start(); (superstar=.=??)
@Override
public void start() {
start(false);
}
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
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(0);是添加一个callback,来看看
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, 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));
}
}
对吧,这里就是在添加回调,那我们回过头去start里面看看startAnimation();
private void startAnimation() {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
}
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
来看看initAnimation();
@CallSuper
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
看看mValues[i].init();
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);
}
}
这里的判断给我们设置了一个估值器,因为我们调用的是ofint,所以这里对应sIntEvaluator,到了这一步这个方法就差不多了,那我们再再回到start方法看看我们的最后一个方法(终于最末尾了啊)setCurrentPlayTime(0);
public void setCurrentPlayTime(long playTime) {
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
setCurrentFraction(fraction);
}
public void setCurrentFraction(float fraction) {
initAnimation();
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(currentIterationFraction);
}
这里都是一些时间变量的计算,合成一个currentIterationFraction对象,最后调用animateValue(currentIterationFraction);这里我们要同时看子类跟父类的两个方法
@Override
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;
}
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
}
@CallSuper
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
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);
}
}
}
先讲父类,这里获取了插值器,计算出了value,然后调用onAnimationUpdate去发送通知,好,回到子类,子类除了调用父类animateValue之外,遍历了numValues,去调用里面的setAnimatedValue(target);
@Override
void setAnimatedValue(Object target) {
if (mIntProperty != null) {
mIntProperty.setValue(target, mIntAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mIntAnimatedValue);
return;
}
if (mJniSetter != 0) {
nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
return;
}
if (mSetter != null) {
try {
mTmpValueArray[0] = mIntAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
这里通过反射mSetter.invoke(target, mTmpValueArray);来修改属性的值,到这里为止,就是动画一帧所走过的流程,那么其他帧的动画执行在哪呢,我们回到start方法-addAnimationCallback(0)-getAnimationHandler().addAnimationFrameCallback(this, delay);这里其实是注册了一个AnimationHandler.AnimationFrameCallback
interface AnimationFrameCallback {
boolean doAnimationFrame(long frameTime);
void commitAnimationFrame(long frameTime);
}
我们找到doAnimationFrame的实现,在 ValueAnimator
public final boolean doAnimationFrame(long frameTime) {
.....
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
.....
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
关键我们是来看animateBasedOnTime这里,这里忽略了一些变量计算,最后其实还是调用了animateValue(currentIterationFraction);所以说我们每产生一次回调,都会去执行一轮帧播放的流程,那么在那里一直产生回调的呢?在 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);
}
}
};
了解 VSync 的同学都知道,Andorid 中的重绘就是由Choreographer在 1 秒内产生 60 个 vsync 来通知 view tree 进行 view 的重绘的。而 vsync 产生后会调用它的监听者回调接口 Choreographer.FrameCallback,也就是说,只要向Choreographer注册了这个接口,就会每 1 秒里收到 60 次回调。因此,在这里就实现了不断地调用 doAnimationFrame() 来驱动动画了。
那么整个动画执行的流程大概就是这样走下来的,我们来简单总结一下几个要点:
1.动画是由许多帧组成的
2.PropertyValuesHolder封装了帧的数据,并被动画类加以利用
3.利用Choreographer.FrameCallback的回调方法,来不停的调用帧执行流程
3.在计算变量数据的时候,插值器会去影响到最后输出的数据,从而达到某些效果
4.通过反射的原理,去修改属性的值,连贯起来出现了动画
浅析结束。
上一篇: Static keyword