Android源码分析—属性动画的工作原理

前言

本文为Android动画系列的最后一篇文章,通过对源码的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画。但是,由于动画的底层实现已经深入到jni层,并且涉及到显示子系统,因此,深入地分析动画的底层实现不仅比较困难而且意义不大,因此,本文的分析到jni层为止。

 

Android动画系列:

android动画简介

Android动画进阶—使用开源动画库nineoldandroids

Android属性动画深入分析:让你成为动画牛人

Android源码分析—属性动画的工作原理

属性动画的原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去拿属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多,下面看源码分析。

源码分析

首先我们要找一个入口,就从ObjectAnimator.ofInt(mButton, width, 500).setDuration(5000).start()开始吧,其他动画都是类似的。

看ObjectAnimator的start方法

 

?
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
37
38
39
40
41
42
43
44
45
@Override
public void start() {
     // See if any of the current active/pending animators need to be canceled
     AnimationHandler handler = sAnimationHandler.get();
     if (handler != null ) {
         int numAnims = handler.mAnimations.size();
         for ( int i = numAnims - 1 ; i >= 0 ; i--) {
             if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                 ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                 if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                     anim.cancel();
                 }
             }
         }
         numAnims = handler.mPendingAnimations.size();
         for ( int i = numAnims - 1 ; i >= 0 ; i--) {
             if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
                 ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
                 if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                     anim.cancel();
                 }
             }
         }
         numAnims = handler.mDelayedAnims.size();
         for ( int i = numAnims - 1 ; i >= 0 ; i--) {
             if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
                 ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
                 if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                     anim.cancel();
                 }
             }
         }
     }
     if (DBG) {
         Log.d(ObjectAnimator, Anim target, duration:  + mTarget + ,  + getDuration());
         for ( int i = 0 ; i < mValues.length; ++i) {
             PropertyValuesHolder pvh = mValues[i];
             ArrayList<keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
             Log.d(ObjectAnimator,    Values[ + i + ]:  +
                 pvh.getPropertyName() + ,  + keyframes.get( 0 ).getValue() + ,  +
                 keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1 ).getValue());
         }
     }
     super .start();
}</keyframe>
说明:上面的代码别看那么长,其实做的事情很简单,首先会判断一下,如果当前动画、等待的动画(Pending)和延迟的动画(Delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉,接下来那一段是log,再接着就调用了父类的super.start()方法,因为ObjectAnimator继承了ValueAnimator,所以接下来我们看一下ValueAnimator的Start方法

 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void start( boolean playBackwards) {
     if (Looper.myLooper() == null ) {
         throw new AndroidRuntimeException(Animators may only be run on Looper threads);
     }
     mPlayingBackwards = playBackwards;
     mCurrentIteration = 0 ;
     mPlayingState = STOPPED;
     mStarted = true ;
     mStartedDelay = false ;
     mPaused = false ;
     AnimationHandler animationHandler = getOrCreateAnimationHandler();
     animationHandler.mPendingAnimations.add( this );
     if (mStartDelay == 0 ) {
         // This sets the initial value of the animation, prior to actually starting it running
         setCurrentPlayTime( 0 );
         mPlayingState = STOPPED;
         mRunning = true ;
         notifyStartListeners();
     }
     animationHandler.start();
}
说明:上述代码最终会调用AnimationHandler的start方法,这个AnimationHandler并不是Handler,它是个Runnable。看下它的代码,通过代码我们发现,很快就调到了jni层,不过jni层最终还是要调回来的。它的run方法会被调用,这个Runnable涉及到和底层的交互,我们就忽略这部分,直接看重点:ValueAnimator中的doAnimationFrame方法

 

 

?
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
final boolean doAnimationFrame( long frameTime) {
     if (mPlayingState == STOPPED) {
         mPlayingState = RUNNING;
         if (mSeekTime < 0 ) {
             mStartTime = frameTime;
         } else {
             mStartTime = frameTime - mSeekTime;
             // Now that we're playing, reset the seek time
             mSeekTime = - 1 ;
         }
     }
     if (mPaused) {
         if (mPauseTime < 0 ) {
             mPauseTime = frameTime;
         }
         return false ;
     } else if (mResumed) {
         mResumed = false ;
         if (mPauseTime > 0 ) {
             // Offset by the duration that the animation was paused
             mStartTime += (frameTime - mPauseTime);
         }
     }
     // The frame time might be before the start time during the first frame of
     // an animation.  The current time must always be on or after the start
     // time to avoid animating frames at negative time intervals.  In practice, this
     // is very rare and only happens when seeking backwards.
     final long currentTime = Math.max(frameTime, mStartTime);
     return animationFrame(currentTime);
}
注意到上述代码末尾调用了animationFrame方法,而animationFrame内部调用了animateValue,下面看animateValue的代码

 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 );
         }
     }
}
上述代码中的calculateValue方法就是计算每帧动画所对应的属性的值,下面着重看一下到底是在哪里调用属性的get和set方法的,毕竟这个才是我们最关心的。

 

get方法:在初始化的时候,如果属性的初始值没有提供,则get方法将会被调用。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void setupValue(Object target, Keyframe kf) {
     if (mProperty != null ) {
         kf.setValue(mProperty.get(target));
     }
     try {
         if (mGetter == null ) {
             Class targetClass = target.getClass();
             setupGetter(targetClass);
             if (mGetter == null ) {
                 // Already logged the error - just return to avoid NPE
                 return ;
             }
         }
         kf.setValue(mGetter.invoke(target));
     } catch (InvocationTargetException e) {
         Log.e(PropertyValuesHolder, e.toString());
     } catch (IllegalAccessException e) {
         Log.e(PropertyValuesHolder, e.toString());
     }
}

 

set方法:当动画的下一帧到来的时候,PropertyValuesHolder中的setAnimatedValue方法会将新的属性值设置给对象,调用其set方法

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
         }
     }
}

总结

我觉得这篇源码分析写的逻辑有点混乱,希望不要给大家带来误导。从源码上来说,属性动画的源码逻辑层次有点跳跃,不过没关系,大家只要了解属性动画的工作原理就好,源码的作用在于让我们发现其工作原理的确如此。到此为止,Android动画系列已经全部完成,十分感谢大家阅读,希望能给大家带来一点帮助!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值