标签:
NineoldAndroids是Github上一个著名的动画库,简单来说,NineOldAndroids是一个向下兼容的动画库,主要是使低于API 11的系统也能够使用View的属性动画。
网上已经有一些文章,介绍了这个库的设计,包括类结构和思想,例如
NineOldAnimations 源码解析
NineoldAndroids动画库源码分析
上面两篇文章都比较详细的介绍了NineoldAndroids的源码,可以说为大家看源码带来很大的方便。
那为什么我还要写这篇文章呢?
我们来看NineoldAndroids的类结构图:
因为NineoldAndroids的类结构比较复杂,即使单纯看上面两篇文章,也可能把人搞糊涂。
本篇文章将剥离NineoldAndroids的具体细节,尝试只是显示其核心功能,也就是说写出一个简易的NineoldAndroids,并且在这个过程当中,了解Android实现动画的原理和思想。
一理通百理明,与君共勉。
现在我们先来看看该动画库的使用:
public class MainActivity extends Activity {
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.mtext);
//Value动画,设置目标值为3000
CValueAnimator valueAnimator = CValueAnimator.ofInt(1000,2000,3000);
//设置动画时间
valueAnimator.setDuration(4000);
valueAnimator.addUpdateListener(new CValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(CValueAnimator animation) {
//将动画值,更新到textView
mTextView.setText(animation.getAnimatedValue() + "");
mTextView.setTranslationY((Integer) animation.getAnimatedValue());
mTextView.invalidate();
}
});
//启动动画
valueAnimator.start();
}
}
使用方式和NineoldAndroids完全一样,也和Android原生的方式一样,对于使用过动画效果的朋友来说,应该非常简单。
首先来看类设计图,这图相比原来的NineoldAndroids,做了很多精简,只是希望大家更加容易看懂NineoldAndroids的本质。
在进行下一步的分析之前,我们先来了解一下一些核心的类以及它们的作用。
在调用start()方法之前,我们使用
CValueAnimator valueAnimator = CValueAnimator.ofInt(1000,2000,3000);
做了动画的初始化工作,那么我们具体做了上面呢?
简单而言,就是把传入的属性值,例如例子中是1000,2000,3000,封装成关键帧对象CKeyFrame。
所谓关键帧,就是在动画过程中一定要出现的帧。
我们知道,所谓动画也不可能是完全连续的,肯定会有一些间隔,只是间隔小于人眼视觉暂留时间,所以看起来就是连续的了。
所以从1000-2000这个过程,也不可能是完全连续的,也许是1000,1100,…1900,2000
其中一些帧就被丢失了,绝对不能丢失的帧,称为关键帧。
关键帧保留两个属性,一个是该帧所在的时间(其实是一个百分比),一个是帧值。
public abstract class CKeyframe implements Cloneable {
/**
* 时间
*/
float mFraction;
/**
* 属性值类型
*/
Class mValueType;
/**
* 插值器
*/
private /*Time*/Interpolator mInterpolator = null;
public static CKeyframe ofInt(float fraction, int value) {
return new IntCKeyframe(fraction, value);
}
public static CKeyframe ofInt(float fraction) {
return new IntCKeyframe(fraction);
}
public abstract Object getValue();
/**
* INT类型值得关键帧
*/
public static class IntCKeyframe extends CKeyframe {
/**
* 关键帧的值
*/
int mValue;
IntCKeyframe(float fraction, int value) {
mFraction = fraction;
mValue = value;
mValueType = int.class;
}
IntCKeyframe(float fraction){
mFraction = fraction;
mValueType = int.class;
}
public int getIntValue() {
return mValue;
}
public void setValue(Object value) {
if (value != null && value.getClass() == Integer.class) {
mValue = ((Integer)value).intValue();
}
}
@Override
public Object getValue() {
return mValue;
}
}
public float getFraction() {
return mFraction;
}
public /*Time*/Interpolator getInterpolator() {
return mInterpolator;
}
}
我们生成关键帧对象以后,将关键帧存入一个集合,称为关键帧集合,也就是CKeyFrameSet类。
CKeyFrameSet类中有一个CTypeEvaluator成员对象,这对象可以通过当前动画进行的百分比,计算出两个关键帧之间的值。
public interface CTypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}
public class IntCEvaluator implements CTypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
可以看到,IntCEvaluator其实就是一个线性计算,fraction是百分比,startValue是起始值,endValue是目标值,不同的fraction会产生不同的结果。
显然对于两个关键帧来说,前一个关键帧的值,就是起始值,后一个关键帧的值,就是目标值。
在CKeyFrameSet中是这样调用这个方法:
public int getIntValue(float fraction) {
if (mNumKeyframes == 2) {//只有两个关键帧的情况
if (firstTime) {
firstTime = false;
firstValue = ((CKeyframe.IntCKeyframe) mKeyframes.get(0)).getIntValue();
lastValue = ((CKeyframe.IntCKeyframe) mKeyframes.get(1)).getIntValue();
deltaValue = lastValue - firstValue;
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
if (mEvaluator == null) {
return firstValue + (int)(fraction * deltaValue);
} else {
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();
}
}
....
CKeyframe.IntCKeyframe prevKeyframe = (CKeyframe.IntCKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {//多个关键帧
CKeyframe.IntCKeyframe nextKeyframe = (CKeyframe.IntCKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
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();
}
方法有点长,大家可以看只有两个关键帧的情况是怎么计算的,就比较简单,其实就是线性计算。
问题是,难道每次我们都希望1000-2000直接是线性增长的吗?如果我希望先快后慢呢?
了解插值器的朋友,应该就明白,这个功能我们可以通过Interpolator去做,但是Interpolator改变的是fraction的增长速度,也就是加速度(勉强可以这样理解)。
从而实现非线性效果,所以显然CKeyframeSet要持有一个Interpolator对象。
顾名思义,CPropertyValuesHolder就是持有属性和值得一个类。
CPropertyValuesHolder是动画库中的一个核心类,但是在本简易库削减了其功能,因为我们只需要实现值得变化,没有针对具体的属性,例如scale,rotate等,所以不需要提供View属性修改的方法。
其实这个类,是为ObjectAnimator做了比较大的准备,但是本篇文章不涉及。
我们关注的是,这个类持有CKeyFrameSet。
public class CPropertyValuesHolder implements Cloneable {
/**
* 属性名称
*/
String mPropertyName;
/**
* 关键帧集合
*/
CKeyframeSet mKeyframeSet = null;
private static final CTypeEvaluator sIntEvaluator = new IntCEvaluator();
private static final CTypeEvaluator sFloatEvaluator = new FloatCEvaluator();
private CTypeEvaluator mEvaluator = sIntEvaluator;
/**
* 属性值
*/
private Object mAnimatedValue;
/**
* 属性值类型
*/
Class mValueType;
private CPropertyValuesHolder(String propertyName) {
mPropertyName = propertyName;
}
/**
* 返回一个属性值类型为int的CPropertyValuesHolder
* @param propertyName
* @param values
* @return
*/
public static CPropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntCPropertyValuesHolder(propertyName, values);
}
/**
* 属性值类型为int的CPropertyValuesHolder
*/
static class IntCPropertyValuesHolder extends CPropertyValuesHolder {
IntCKeyframeSet mIntKeyframeSet;
public IntCPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);
setIntValues(values);
}
@Override
public void setIntValues(int... values) {
super.setIntValues(values);
mIntKeyframeSet = (IntCKeyframeSet) mKeyframeSet;
}
}
/**
* 返回当前属性值
* @return
*/
Object getAnimatedValue() {
return mAnimatedValue;
}
/**
* 设置属性值类型,这里具体到int类型
* 对于每个Int类型值,例如100,200,1000
* 生成一个CKeyFrame关键帧对象,并且将这些对象包装成一个set集合
* @param values
*/
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframeSet = CKeyframeSet.ofInt(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
mKeyframeSet.setEvaluator(mEvaluator);
}
}
/**
* 让CKeyframeSet通过关键帧计算属性值
* @param fraction
*/
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
}
/**
* CPropertyValuesHolder是一个包装类
* 可以看做是,需要动画的属性或者值的对象实例
*/
CPropertyValuesHolder[] mValues;
/**
* 属性值类型为int的动画
* @param values
* @return
*/
public static CValueAnimator ofInt(int... values) {
CValueAnimator anim = new CValueAnimator();
anim.setIntValues(values);
return anim;
}
/**
* 根据一系列属性值,生成CPropertyValuesHolder
* CPropertyValuesHolder这个类的意义就是,持有某个属性的一系列值,
* 例如"scale(缩放属性)",其若干个值为100,200,1000等。
* 也就是说在规定时间内,"scale"的值会从100增长到1000
* @param values
*/
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
//属性名称为"",说明只是数值改变,和具体属性无关
setValues(new CPropertyValuesHolder[]{CPropertyValuesHolder.ofInt("", values)});
} else {
CPropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
public void setValues(CPropertyValuesHolder... values) {
mValues = values;
mInitialized = false;
}
原理就是根据1000,2000,3000,生成了一个CPropertyValuesHolder对象,并且将它保存了起来。
目前万事俱备,只等调用start()方法开始动画了。
直接来看简化过后的start()方法
public void start() {
if (Looper.myLooper() == null) {//当前线程必须调用了Looper.loop()方法
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mPlayingState = STOPPED;
sPendingAnimations.get().add(this);
/**
* 初始化动画时间,也就是设置起始运行时间为0
* 并且计算起始属性值值
*/
setCurrentPlayTime(getCurrentPlayTime());
mPlayingState = STOPPED;
mRunning = true;
//通知监听器,动画开始
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this);
}
}
//Handler,通过自己给自己发送消息,实现不断进行动画
AnimationHandler animationHandler = sAnimationHandler.get();
if (animationHandler == null) {
animationHandler = new AnimationHandler();
sAnimationHandler.set(animationHandler);
}
animationHandler.sendEmptyMessage(ANIMATION_START);
}
动画过程我们可以这样想:
1、获取当前时间为startTime,即动画起始时间,并且初始化动画状态,例如1000,2000,3000,那么setCurrentPlayTime()方法的其中一个工作就是初始化状态为1000
2、通知动画开始监听器,动画开始
3、使用AnimationHandler实现循环,首先给AnimationHandler发送了一条ANIMATION_START信息
显然,主要工作就是在AnimationHandler里面进行的
/**
* 该handler用于处理两个消息
* ANIMATION_START也就是动画开始
* ANIMATION_FRAME也就是运行某一帧
*/
private static class AnimationHandler extends Handler {
@Override
public void handleMessage(Message msg) {
boolean callAgain = true;
//当前运行动画队列
ArrayList<CValueAnimator> animations = sAnimations.get();
switch (msg.what) {
case ANIMATION_START:
//当前等候动画队列
ArrayList<CValueAnimator> pendingAnimations = sPendingAnimations.get();
if (animations.size() > 0) {
callAgain = false;
}
while (pendingAnimations.size() > 0) {//如果等候的动画大于0
ArrayList<CValueAnimator> pendingCopy =
(ArrayList<CValueAnimator>) pendingAnimations.clone();
pendingAnimations.clear();
int count = pendingCopy.size();
for (int i = 0; i < count; ++i) {
CValueAnimator anim = pendingCopy.get(i);
// If the animation has a startDelay, place it on the delayed list
anim.startAnimation();//启动这些动画
}
}
case ANIMATION_FRAME:
//当前时间
long currentTime = AnimationUtils.currentAnimationTimeMillis();
//当前已经结束的动画队列
ArrayList<CValueAnimator> endingAnims = sEndingAnims.get();
//正在运行的对话数量
int numAnims = animations.size();
int i = 0;
while (i < numAnims) {
CValueAnimator anim = animations.get(i);
if (anim.animationFrame(currentTime)) {//更新每个运行动画的数值,如果已经结束,加入endingAnims对象
endingAnims.add(anim);
}
if (animations.size() == numAnims) {
++i;
} else {
//在动画运行过程中,可能有些动画被取消
--numAnims;
endingAnims.remove(anim);
}
}
if (endingAnims.size() > 0) {
for (i = 0; i < endingAnims.size(); ++i) {
endingAnims.get(i).endAnimation();
}
endingAnims.clear();
}
// If there are still active or delayed animations, call the handler again
// after the frameDelay
//如果还有活动的动画,在默认每帧间隔时间以后,再次调用,更新属性值
if (callAgain && (!animations.isEmpty())) {
sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
(AnimationUtils.currentAnimationTimeMillis() - currentTime)));
}
break;
}
}
}
这个类做了这些工作:
其实就是调用了等待队列pendingAnimations中CValueAnimator对象的startAnimation()方法
/**
* 启动动画
*/
private void startAnimation() {
//初始化动画
initAnimation();
//将等候队列中的动画,加入运行对象
sAnimations.get().add(this);
}
在该方法中,初始化了动画(其实这里调用initAnimation()没有实际作用,因为之前已经初始化过了);
然后就是将动画放入运行队列。
我们注意到ANIMATION_START状态以后,并没有使用break,所以会接着执行ANIMATION_FRAME
对每个运行动画,调用其了animationFrame()方法
/**
* 根据当前时间,计算运行百分比,然后调用animateValue更新当前属性值
* @param currentTime
* @return
*/
boolean animationFrame(long currentTime) {
boolean done = false;
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime < 0) {
mStartTime = currentTime;
} else {
mStartTime = currentTime - mSeekTime;
// Now that we‘re playing, reset the seek time
mSeekTime = -1;
}
}
switch (mPlayingState) {
case RUNNING:
case SEEKED:
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
if (fraction >= 1f) {//百分比大于1,结束动画
done = true;
fraction = Math.min(fraction, 1.0f);
}
animateValue(fraction);
break;
}
return done;
}
这个方法会根据当前时间,判断动画是否已经结束,如果是,返回true,这些动画就会进入sEndingAnims队列,做最后的结束通知工作。
否则,其实就是调用了自己的CPropertyValuesHolder计算当前属性值
/**
* 根据百分比,更新属性值
* @param fraction
*/
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);//让CPropertyValuesHolder计算属性值
}
//通知监听器,属性值更新
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
走完两个case,新的属性值就计算出来了,我们通过getAnimatedValue()就可以拿到
/**
* 获取当前属性值
* @return
*/
public Object getAnimatedValue() {
if (mValues != null && mValues.length > 0) {
return mValues[0].getAnimatedValue();
}
// Shouldn‘t get here; should always have values unless ValueAnimator was set up wrong
return null;
}
那么怎么让动画继续计算下一个属性值呢?
注意最后
// If there are still active or delayed animations, call the handler again
// after the frameDelay
//如果还有活动的动画,在默认每帧间隔时间以后,再次调用,更新属性值
if (callAgain && (!animations.isEmpty())) {
sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
(AnimationUtils.currentAnimationTimeMillis() - currentTime)));
}
也就是在规定的帧间隔以后,AnimationHandler给自己再次发送一个ANIMATION_FRAME消息,进行下一次属性值的计算。
最后,如上面所说,animationFrame()返回true,才真正结束动画!
到此为止,我要介绍的内容就说完了。
大家只要了解关键帧CKeyframe,CPropertyValuesHolder,估值算法CTypeEvaluator,以及使用handler循环计算动画值的原理
就应该可以了解到NineoldAndroids动画库设计的核心。
文章以及将NineoldAndroids的代码做了很多精简,就是希望大家可以明白本质,如果还有疑问的地方,可以下载我的测试代码,运行感受一下下。
也可以给我留言,一起探讨。
源码下载地址(manimation包是我精简过的代码,animation,util,view三个包中的,是NineoldAndroids的代码,大家可以对比看看)。
版权声明:本文为博主原创文章,转载请注明出处。
打造简易NineoldAndroids动画库,深入理解Android动画原理
标签:
原文地址:http://blog.csdn.net/crazy__chen/article/details/49912029