在android中特效的展示会用到各种各样的属性动画,所以下面来看下android中属性动画的源码,其实在我看来源码是熟悉api和实现相关代码最直接最有效以及最深刻的途径,废话不多说,进入正题:
因为ObjectAnimator是继承自ValueAnimator,会有一些方法的使用覆盖,所以我们先来看ValueAnimator
ValueAnimator
首先 ValueAnimator在android中的使用
// ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200, 100);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
//在这里会有属性动画的监听来获取动画的一个进度 可以粗略理解为在ValueAnimator的过程中对
//动画进行操作处理
}
});
valueAnimator.start();
那么我们分3步来看:
1—ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200, 100);
2—valueAnimator.start();
3—就是值变化的监听:
ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
1.估值器/插值器
1.1估值器
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>int</code> or
* <code>Integer</code>
* @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
代码很简单,只有一个方法:evaluate;其实就是通过传入的百分比,初始值,结束值来retrurn当前的value
1.2插值器
在android中插值器有很多,这里不一一介绍,有兴趣的可以自行查看api:
在这里单举一例:匀减速插值器:
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
在插值器中有一个方法getInterpolation(float input),其实就是根据具体的需求来返回不同的fraction,然后出传入估值器来算出不同阶段的value;
2.源码分析—-ValueAnimator.ofxxx
在valueAnimator API中右许多初始化的api,这里以ValueAnimator.ofInt(0, 200, 100)进行分析,大同小异,殊途同归;这里大家没有必要纠结;接着继续看源码:
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
完成ValuAnimtor的实例化,继续来看 anim.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;
}
上面代码中mValues是用来存储PropertyValuesHolder,在这里做了一系列的判断:当mValues为null或者size为0时,需要通过setValues重新初始化;如果mValues不为null,则重新覆盖mValues[0]; ok~继续看:setValues(PropertyValuesHolder.ofInt(“”, values));中setValues的源码:
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
上面代码,将传入的values数组赋值给mValues,mValuesMap存储values的元素,key为其属性;其实就是初始化和赋值的过程; 下面来看setValues中参数获取的方法:PropertyValuesHolder.ofInt(“”, values);这是比较核心的代码,大家需要着重关注一下:
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntPropertyValuesHolder(propertyName, values);
}
继续往下看: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;
}
在这里有出现一个mKeyframes,这个类在ValueAnimator中是存储key:动画属性(就是百分比),value:目标值; 而mKeyframes的初始化是在super.setIntValues(values)中进行:
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframes = KeyframeSet.ofInt(values);
}
接着看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);
}
在上面的代码中有一些代码的判断逻辑:numKeyframes == 1,就是value传入为一个value,那么有两个关键时间点一个是(0, 0)一个是(1, value)。这个也好理解就是一个开始点一个结束点;如果参数的个数不止一个的时候,根据参数的个数去平分时间点,比如ofInt的初始值是 ofInt(100, 200, 300)那么我们得到的时间点就是(0, 100), (1/2, 200),(1, 300)。三个关键的时间点; 而属性和value的初始化在Keyframe.ofInt,那么我们来看Keyframe.ofInt的源码:
public static Keyframe ofInt(float fraction, int value) {
return new IntKeyframe(fraction, value);
}
IntKeyframe(float fraction, int value) {
mFraction = fraction;
mValue = value;
mValueType = int.class;
mHasValue = true;
}
就是一个初始化的操作~ 所以在ValueAnimator.ofxxx整个过程中就是百分比和value的一个初始化;
3.源码分析—-valueAnimator.start
下面 直接看valueAnimator.start()的源码:
@Override
public void start() {
start(false);
}
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
* <p>The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.</p>
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
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);
}
}
}
首先对其中几个参数进行说明一下:
mReversing:表示当前是否从后面开始播放的;
mSeekFraction:表示动画要跳到的播放时间点(0~1)setCurrentFraction()函数里面设置。
首先来看核心代码:
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);
}
}
判断mStartDelay == 0 || mSeekFraction >= 0 || mReversing;即开始延时==0/跳转的百分比>=0;是否为反向开始 ;然后开始动画: 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();源码:
这里写代码片 /**
* Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
* to calculate animated values.
*/
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);
}
}
public void setEvaluator(TypeEvaluator evaluator) {
mEvaluator = evaluator;
}
看到没有 其实initAnimator就是给动画加了一个估值器,即通过传入插值器返回的百分比来实时计算每一阶段的动画值,一次来实现ValueAnimator;到这里动画就开始启动了; 然后其实ValueAnimator的核心不是start(),而是:
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
//在这里会有属性动画的监听来获取动画的一个进度 可以粗略理解为在ValueAnimator的过程中对
//动画进行操作处理
}
});
其实你可以这样理解,ValueAnimator其实就是对初值渐变为最终值的一种不同渐变变化处理,并且实时获取渐变全程中的数值来投射为某种View的属性变化,进而实现所谓的属性动画ValueAnimator;
4.源码分析—-ValueAnimator.AnimatorUpdateListener()
继续看ValueAniamtor的start()方法
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);
}
}
在InitAnimation之后 开始走setCurrentFraction,当然在这里有一个判断,但是看过源码之后应该可以知道:setCorrentPlayTime的核心也是setCurrentFraction的实现,所以我们直接来看setCurrentFraction源码:
/**
* Sets the position of the animation to the specified fraction. This fraction should
* be between 0 and the total fraction of the animation, including any repetition. That is,
* a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
* and a value of 2 at the end of a reversing animator that repeats once. If
* the animation has not yet been started, then it will not advance forward after it is
* set to this fraction; it will simply set the fraction to this value and perform any
* appropriate actions based on that fraction. If the animation is already running, then
* setCurrentFraction() will set the current fraction to this value and continue
* playing from that point. {@link Animator.AnimatorListener} events are not called
* due to changing the fraction; those events are only processed while the animation
* is running.
*
* @param fraction The fraction to which the animation is advanced or rewound. Values
* outside the range of 0 to the maximum fraction for the animator will be clamped to
* the correct range.
*/
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 {
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
继续对Aniamator的startTime和播放比率fraction进行控制,然后继续看 animateValue(currentIterationFraction);
@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);
}
}
}
在这里通过传入的fraction,通过mValues[i].calculateValue(fraction);进行进一步处理,而前面有说过mValues是PropertyValuesHolder的数组,那么定位到该类的calculateValue方法:
@Override
void calculateValue(float fraction) {
mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
}
接着看getIntvalue:
@Override
public int getIntValue(float fraction) {
if (fraction <= 0f) {
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
} else if (fraction >= 1f) {
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
}
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
可以看到代码比较多,首先来看
fraction <= 0f就是第一个时间点和第二个时间点
fraction >= 1f就是最后一个时间点和倒数第二个时间点
大体的value值获取:
首先在ValueAnimator的ofInt初始化阶段会把fraction和value以key–value的形式存入Keyframe中然后在这里获取,通过插值器根据不同的要求获取fraction,然后估值器返回value
这样在会调接口:
ValueAnimator.AnimatorUpdateListener()—onAnimationUpdate(ValueAnimator animation) 通过int currentValue = (Integer) animation.getAnimatedValue();来获取value进行不同速率的动画实现
源码好多,而且不同android Api 有所差异,这是6.0下的ValueAnimator源码;希望有所收益,右兴趣的可以关注一下,感谢阅读,共同进步~