【源码解析】Android属性动画内部实现

想在Android中做很炫酷的动画效果,很多时候都可以选择使用属性动画。本篇博客将分析属性动画的实现源码,带你深入的了解Android属性动画的内部实现机制。

另外,关于属性动画具体如何使用,还可参考以下两篇文章:

Android 属性动画(Property Animation)解析(上)

Android 属性动画(Property Animation)解析(下)

在源码分析之前,我们首先要有一个明确的思路,例如:源码的入口的选择、甚至对其实现进行简单的猜测,源码分析相当于一个验证的过程,带着一个目标去看源码,这样的话,分析和理解起来更为方便。

对于实现属性动画,最常用的类就是ObjectAnimator了,只需要简单的设置目标view,属性,以及目标值等必要属性,调用一下start();我们的动画就完成了。

类似如下代码:

?
1
2
3
4
5
6
ObjectAnimator  
   .ofInt(target,propName,values[])  
   .setInterpolator(LinearInterpolator)  
   .setEvaluator(IntEvaluator)  
   .setDuration( 500 )  
   .start();

上述代码很好理解,即设置动画作用的view,作用的属性,动画开始、结束、以及中间的任意一个属性值;

然后是设置插值器,当然了插值器这个词比较难理解,我要是说例如:AccelerateInterpolator、LinearInterpolator,然后设置估值算法,这个看名字挺高端,其实内部实现尤其简单:return (int)(startInt + fraction * (endValue - startInt)); 开始值,加上当前的属性改变的百分比*(结束-开始)

当然了,这个百分比是fraction ,其实就是上面的插值器算出来的。比如线性插值器:fraction 值就是currentTime - mStartTime) / mDuration,动画的运行时间/总设置时间。

然后是设置动画事件,最后start()。

那么问题来了,根据上面这些参数,如果我要你设计一个属性动画框架,你怎么做?

这个嘛,好整,拿到上述参数之后,start()中开启一个定时器,去执行一个任务;在任务内部,根据Interpolator计算出来的fraction,交给Evaluator,得到属性当前应该设置的值,然后反射设置tagert的指定属性,ok。嗯,大体上应该就是这样,当然了,源码的实现肯定复杂很多,但是万变不离其宗,所以接下来的源码阅读,就是去验证我们的这个答案。

一、源码分析

好了,之后我们源码的入口就是上述代码了,不过貌似上述代码调用了好几个方法。但是我觉得start之前的代码,无非是初始化实例,设置一些成员变量。

首先我们看ofInt,这里为了简单,我们的ofInt中的values参数,默认就一个,类似.ofInt(view, "translationX", 300) ;

1、ofInt

?
1
2
3
4
5
public  static  ObjectAnimator ofInt(Object target, String propertyName,  int ... values) {  
         ObjectAnimator anim =  new  ObjectAnimator(target, propertyName);  
         anim.setIntValues(values);  
         return  anim;  
     }

首先调用ObjectAnimator的构造方法传入了一个target和propName,估计就是创建对象,然后记录下target和propName,简单看下。

?
1
2
3
4
5
6
7
8
9
private  ObjectAnimator(Object target, String propertyName) {  
        mTarget = target;  
        setPropertyName(propertyName);  
    }  
public  void  setPropertyName(String propertyName) {  
       //...  
        mPropertyName = propertyName;  
       mInitialized =  false ;  
    }

记录完成target,propName以后,调用setIntValues

?
1
2
3
4
@Override  
     public  void  setIntValues( int ... values) {  
             setValues(PropertyValuesHolder.ofInt(mPropertyName, values));    
     }

可以看到,把我们的propName,和values传入到了一个PropertyValuesHolder的ofInt方法中,去构造一个PropertyValuesHolder对象,这个对象是干什么的呢?从字面上看,是保存view在动画期间的属性和值,记住是动画期间的。继续往下看:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public  static  PropertyValuesHolder ofInt(String propertyName,  int ... values) {  
         return  new  IntPropertyValuesHolder(propertyName, values);  
     }  
   public  IntPropertyValuesHolder(String propertyName,  int ... values) {  
             mPropertyName = propertyName;  
             setIntValues(values);  
         }  
@Override  
         public  void  setIntValues( int ... values) {  
             mValueType =  int . class ;  
             mKeyframeSet = KeyframeSet.ofInt(values);  
             mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;  
         }

可以看到在IntPropertyValuesHolder内部存储了我们的propertyName;,然后又调用了setIntValues,存储了我们的mValueType ,此外还存了一个mIntKeyframeSet。

这里又出现一个新名词,叫做mKeyframeSet,这个是由KeyframeSet.ofInt(values);得到的。

那么这个KeyframeSet是什么呢?单纯的理解是Keyframe的集合,而Keyframe叫做关键帧,为一个动画保存time/value(时间与值)对。

那么我们去看看它是如何通过KeyframeSet.ofInt(values);去构造与保存的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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  {  
             //...  
         }  
         return  new  IntKeyframeSet(keyframes);  
     }  
  public  IntKeyframeSet(IntKeyframe... keyframes) {  
         mNumKeyframes = keyframes.length;  
         mKeyframes =  new  ArrayList<Keyframe>();  
         mKeyframes.addAll(Arrays.asList(keyframes));  
         mFirstKeyframe = mKeyframes.get( 0 );  
         mLastKeyframe = mKeyframes.get(mNumKeyframes -  1 );  
         mInterpolator = mLastKeyframe.getInterpolator();  
     }

这里代码跳跃比较大,部分代码我来解释。

根据我们的values的长度,构造了keyframes数组,然后分别通过Keyframe的ofInt方法,去构造keyframe对象,其实在内部:

?
1
2
3
4
5
6
7
8
9
10
11
IntKeyframe( float  fraction,  int  value) {  
             mFraction = fraction;  
             mValue = value;  
             mValueType =  int . class ;  
             mHasValue =  true ;  
         }  
   
         IntKeyframe( float  fraction) {  
             mFraction = fraction;  
             mValueType =  int . class ;  
         }

就简单存了一下fraction,和value;当然了,我们这里values只有一个值,所以构造了两个Keyframe。

拿到初始化完成的keyframes数组以后,将其传入了KeyframeSet的构造方法,初始化了KeyframeSet内部的一些成员变量。

?
1
2
3
4
5
6
7
8
public  IntKeyframeSet(IntKeyframe... keyframes) {  
         mNumKeyframes = keyframes.length;  
         mKeyframes =  new  ArrayList<Keyframe>();  
         mKeyframes.addAll(Arrays.asList(keyframes));  
         mFirstKeyframe = mKeyframes.get( 0 );  
         mLastKeyframe = mKeyframes.get(mNumKeyframes -  1 );  
         mInterpolator = mLastKeyframe.getInterpolator();  
     }

存了有多少关键帧,开始帧,结束帧,以及插值器。

到此,我们的(PropertyValuesHolder.ofInt在彻底返回,可以看到这个过程中,我们成功的为PropertyValuesHolder对象赋值了propName,valueType,keyframeSet .keyframeset中存了Keyframe集合,keyframe中存储了(fraction , valuetype , value , hasValue)。

最后,叫 PropertyValuesHolder 交给我们的 ObjectAnimator的setValues方法。

?
1
2
3
4
5
6
7
8
9
10
11
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 ;  
    }

首先记录了mValues,注意这里的values是PropertyValuesHolder类型的,然后通过一个mValueMap记录:key为属性的名称,值为PropertyValuesHolder 。 

好了,到此我们的ofInt结束了,晕否,其实还好。如果你晕了,我帮你总结下:ofInt就是记录了target,propName,values(是将我们传入的int型values,辗转转化成了PropertyValuesHolder),以及一个mValueMap,这个map的key是propName,value是PropertyValuesHolder,在PropertyValuesHolder内部又存储了proprName, valueType , keyframeSet等等。好了,接下来会轻松点。

2、setInterpolator

?
1
2
3
4
5
6
7
8
@Override  
    public  void  setInterpolator(TimeInterpolator value) {  
        if  (value !=  null ) {  
            mInterpolator = value;  
        else  {  
            mInterpolator =  new  LinearInterpolator();  
        }  
    }

3、setEvaluator

?
1
2
3
4
5
public  void  setEvaluator(TypeEvaluator value) {  
        if  (value !=  null  && mValues !=  null  && mValues.length >  0 ) {  
            mValues[ 0 ].setEvaluator(value);  
        }  
    }

记得我们这里的mValue吧,在ofInt里面初始化的,类型是PropertyValuesHolder。然后调用了PropertyValuesHolder.setEvalutor

?
1
2
3
4
public  void  setEvaluator(TypeEvaluator evaluator) {  
       mEvaluator = evaluator;  
       mKeyframeSet.setEvaluator(evaluator);  
   }

记录了一下估值算法,然后再将其传给KeyframeSet对象:

?
1
2
3
public  void  setEvaluator(TypeEvaluator evaluator) {  
         mEvaluator = evaluator;  
     }

可以看到,我们把估值算法交给了PropertyValuesHolder以及KeyframeSet。

接下来,最后一个属性,duration

4、setDuration

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// How long the animation should last in ms  
    private  long  mDuration = ( long )( 300  * sDurationScale);  
    private  long  mUnscaledDuration =  300 ;  
    private  static  float  sDurationScale =  1 .0f;  
   
    public  ObjectAnimator setDuration( long  duration) {  
        if  (duration <  0 ) {  
            throw  new  IllegalArgumentException( "Animators cannot have negative duration: "  +  
                    duration);  
        }  
        mUnscaledDuration = duration;  
        mDuration = ( long )(duration * sDurationScale);  
        return  this ;  
    }

就是简单在mDuration中记录了一下动画的持续时间,这个sDurationScale默认为1,貌似是用于调整,观察动画的,比如你可以调整为10,动画就会慢10倍的播放。

好了,到此该设置的设置完成了。

小小总结一下:ofInt中实例化了一个ObjectAnimator对象,然后设置了target,propName,values(PropertyValuesHolder) ;然后分别在setInterpolator,setDuration设置了Interpolator和duration。其中setEvaluator是给values[0],以及keyframeSet设置估值算法。

PropertyValueHolder实际上是IntPropertyValueHolder类型对象,包含propName,valueType,keyframeSet .

keyframeset中存了Keyframe集合,keyframe中存储了(fraction , valuetype , value , hasValue)。

以上都比较简单,关键就是看start()方法中,如何将这些属性进行合理的处理调用神马的。

5、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
@Override  
     public  void  start() {  
         super .start();  
     }  
ValueAnimator  
@Override  
     public  void  start() {  
         start( false );  
     }  
ValueAnimator  
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();  
     }

最终调用了ValueAnimator的statr(playBackwards)方法;

15-20行:设置了关于动画的一些标志位,mPlayingBackwards 表示动画是否reverse;mCurrentIteration 记录当前的动画的执行次数(与setRepeatCount有关);mPlayingState 动画的状态为STOPPED;还有些其他的标志位;

21行:生成一个AnimationHandler对象,getOrCreateAnimationHandler就是在当前线程变量ThreadLocal中取出来,没有的话,则创建一个,然后set进去。

AnimationHandler中包含一些List集合用于存储各种状态的ValueAnimator。

22行:将当前ValueAnimator对象,加入  animationHandler.mPendingAnimations 集合。

23行:未设置mStartDelay,默认为0,则进入循环;

24行:setCurrentPlayTime(0);一会需要细说

25-26行:设置些状态;

27行:回调监听动画的接口AnimatorListener的onAnimationStart方法,如果你设置了回调监听,此时就会进行回调;

最后30行:调用animationHandler.start();需要细说;

好了,有两个方法需要细说,首先看setCurrentPlayTime(0)

?
1
2
3
4
5
6
7
8
9
10
public  void  setCurrentPlayTime( long  playTime) {  
        initAnimation();  
        long  currentTime = AnimationUtils.currentAnimationTimeMillis();  
        if  (mPlayingState != RUNNING) {  
            mSeekTime = playTime;  
            mPlayingState = SEEKED;  
        }  
        mStartTime = currentTime - playTime;  
        doAnimationFrame(currentTime);  
    }

首先初始化动画,然后得到当前的系统开始到现在的时间currentTime;设置mSeekTime,设置当前状态为SEEKED;然后使用mSeekTime-playTime得到动画现在需要执行的时间;最后调用 doAnimationFrame(currentTime),稍后看其代码;

关于initAnimation(),实际就是去设置我们ValueAnimator中存储的mValues,也就是IntPropertyValueHolder的mEvaluator;

?
1
2
3
4
5
6
7
8
void  initAnimation() {  
        if  (!mInitialized) {  
            int  numValues = mValues.length;  
            for  ( int  i =  0 ; i < numValues; ++i) {  
                mValues[i].init();  
            }  
            mInitialized =  true ;  
        }

PropertyValuesHolder的init方法:

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

其实就是便利设置PropertyValuesHolder中的mEvaluator属性,默认根据valueType进行判断,IntEvaluator或者FloatEvaluator。

接下来应该看doAnimationFrame(currentTime);了

?
1
2
3
4
5
final  boolean  doAnimationFrame( long  frameTime) {  
          
        final  long  currentTime = Math.max(frameTime, mStartTime);  
        return  animationFrame(currentTime);  
    }

内部调用了:animationFrame(currentTime);

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
boolean  animationFrame( long  currentTime) {  
         boolean  done =  false ;  
         switch  (mPlayingState) {  
         case  RUNNING:  
         case  SEEKED:  
             float  fraction = mDuration >  0  ? ( float )(currentTime - mStartTime) / mDuration : 1f;  
             if  (fraction >= 1f) {  
                //...  
             }  
             if  (mPlayingBackwards) {  
                 fraction = 1f - fraction;  
             }  
             animateValue(fraction);  
             break ;  
         }  
   
         return  done;  
     }

这里通过判断当前动画的状态,给出fraction,默认传入的就是(float)(currentTime - mStartTime) / mDuration,动画执行的时间除以总的时间比值;

接下来调用了animateValue(fraction)

在animateValue的内部,会将传入的fraction交给 mInterpolator.getInterpolation(fraction);方法,获得插值器处理后的fraction;然后在将fraction交给估值算法mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();进行计算得到当前时间点,属性应该的值;最后会反射对我们设置的属性进行设置。

动画如果没结束,应该每隔一定的帧数,再次调用,嗯,的确是这样的,你看到animationFrame最后是不是有个返回值,这个值会在fraction>=1的时候返回true;

我们还是先看看animateValue方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 );  
           }  
       }  
   
      int  numValues = mValues.length;  
       for  ( int  i =  0 ; i < numValues; ++i) {  
           mValues[i].setAnimatedValue(mTarget);  
       }  
   }

首先将fraction交给给 mInterpolator.getInterpolation(fraction);得到计算后的fraction;然后for循环便利调用IntPropertyValueHolder的calculateValue方法:

?
1
2
3
void  calculateValue( float  fraction) {  
       mAnimatedValue = mKeyframeSet.getValue(fraction);  
   }

在其内部,调用了mKeyframeSet的getValue,这里注意我们的IntKeyFrameSet,千万不要看错方法了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override  
     public  Object getValue( float  fraction) {  
         return  getIntValue(fraction);  
     }  
public  int  getIntValue( float  fraction) {  
         if  (mNumKeyframes ==  2 ) {  
             if  (firstTime) {  
                 firstTime =  false ;  
                 firstValue = ((IntKeyframe) mKeyframes.get( 0 )).getIntValue();  
                 lastValue = ((IntKeyframe) 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();  
             }  
         }  
         //...省略了很多代码  
     }

在其内部,因为我们只设置了一个目标属性值,所以只有两个关键帧;然后16-20行,调用估值算法的mEvaluator.evaluate方法,可以看到如果mEvaluator == null直接调用了firstValue + (int)(fraction * deltaValue);其实这个就是IntEvaluator的默认实现。

好了,for循环结束了,经过我们插值器和估值算法得出的值,最终给了IntPropertyValueHolder的mIntAnimatedValue属性。

回到animateValue方法:在animateValue的8-12行,继续回调动画监听onAnimationUpdate(this);方法;

animateValue的15-18行:循环拿到(其实我们就只有一个属性)我们的IntPropertyValueHolder调用setAnimatedValue,进行反射为我们的属性设置值,反射需要一些东西,比如target,propname,以及该属性应该设置的值;这三个参数在哪呢?target作为参数传入了,propName初始化的时候就设置了。

好了,到此,我们属性动画,设置的各种值,经过重重的计算作用到了我们的属性上,反射修改了我们的属性。到此我们已经完成了一大半,但是貌似还少了个,每隔多少帧调用一次。

嗯,的确是的,跨度好大,现在回到我们的start方法,最后一行:调用animationHandler.start();这个还没细说呢。

animationHandler我们上面已经介绍了,存储在当前线程的ThreadLocal里面,里面放了一些集合用于存储各种状态的ObjectAnimator,我们当前的ObjectAnimator对象也存储在其mPendingAnimations的集合中(上面提到过~~)。

?
1
2
3
4
5
6
7
8
9
10
11
12
/** 
          * Start animating on the next frame. 
          */  
         public  void  start() {  
             scheduleAnimation();  
         }  
private  void  scheduleAnimation() {  
             if  (!mAnimationScheduled) {  
                 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION,  this null );  
                 mAnimationScheduled =  true ;  
             }  
         }

start内部最终调用了mChoreographer.postCallback,其中有一个参数是this;至于什么是Choreographer,暂时不用管;但是你需要知道一件事,其实我们的animationHandler是Runnable的子类,而 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);类似与handler发送消息,最终执行这个Runnable的run方法。

说这么多,其实就是一句话,这里调用了animationHandler的 run方法。

?
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
46
47
48
49
public  void  run() {  
             mAnimationScheduled =  false ;  
             doAnimationFrame(mChoreographer.getFrameTime());  
         }  
private  void  doAnimationFrame( long  frameTime) {  
             while  (mPendingAnimations.size() >  0 ) {  
                 ArrayList<ValueAnimator> pendingCopy =  
                         (ArrayList<ValueAnimator>) mPendingAnimations.clone();  
                 mPendingAnimations.clear();  
                 int  count = pendingCopy.size();  
                 for  ( int  i =  0 ; i < count; ++i) {  
                     ValueAnimator anim = pendingCopy.get(i);  
                     // If the animation has a startDelay, place it on the delayed list  
                     if  (anim.mStartDelay ==  0 ) {  
                         anim.startAnimation( this );  
                     else  {  
                         mDelayedAnims.add(anim);  
                     }  
                 }  
             }  
             //...省略了一些代码  
               
   
             // Now process all active animations. The return value from animationFrame()  
             // tells the handler whether it should now be ended  
             int  numAnims = mAnimations.size();  
             for  ( int  i =  0 ; i < numAnims; ++i) {  
                 mTmpAnimations.add(mAnimations.get(i));  
             }  
             for  ( int  i =  0 ; i < numAnims; ++i) {  
                 ValueAnimator anim = mTmpAnimations.get(i);  
                 if  (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {  
                     mEndingAnims.add(anim);  
                 }  
             }  
             mTmpAnimations.clear();  
             if  (mEndingAnims.size() >  0 ) {  
                 for  ( int  i =  0 ; i < mEndingAnims.size(); ++i) {  
                     mEndingAnims.get(i).endAnimation( this );  
                 }  
                 mEndingAnims.clear();  
             }  
   
             // If there are still active or delayed animations, schedule a future call to  
             // onAnimate to process the next frame of the animations.  
             if  (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {  
                 scheduleAnimation();  
             }  
         }

6-20行:while循环,遍历所有在mPendingAnimations中的ObjectAnimator,依次调用anim.startAnimation(this);

在anim.startAnimation(this);内部其实主要就一行代码:handler.mAnimations.add(this); 将当前动画加入animationHandler的mAnimations集合;

26-29行:将animationHandler的mAnimations集合中的每个anim,加入到mTmpAnimations中;

30-35行:依次调用mTmpAnimations中的anim,anim.doAnimationFrame(frameTime)

doAnimationFrame(frameTime)上面已经分析过了,如果返回true,即doAnimationFrame的done为true,则将该动画加入到结束动画集合;

37-43行:循环调用mEndingAnims, mEndingAnims.get(i).endAnimation(this);内部,会将动画移除mAnimations,回调动画监听接口onAnimationEnd;以及重置各种标志变量;

46-48行:如果mAnimations不为null,则再次调用scheduleAnimation();

前面已经描述过animationHandler的 run方法中通过计算属性应该的值,反射设置;加上我们这里的动画没结束,就会再次调用该run方法内部一致的方法。

好了到此,我们的属性动画的流程已经完美跑通了。

二、总结

其实看源码的目的,最终就是为了总结,这么长的代码谁也记不住,所以看完记得总结。

ofInt中实例化了一个ObjectAnimator对象,然后设置了target,propName,values(PropertyValuesHolder) ;然后分别在setInterpolator,setDuration设置了Interpolator和duration。其中setEvaluator是给PropertyValuesHolder,以及keyframeSet设置估值算法。

PropertyValueHolder实际上是IntPropertyValueHolder类型对象,包含propName,valueType,keyframeSet .

keyframeset中存了Keyframe集合,keyframe中存储了(fraction , valuetype , value , hasValue)。

上述其实都是设置各种值什么的。真正核心要看start。

start()中:

首先,更新动画各种状态,然后初步计算fraction为(currentTime - mStartTime) / mDuration;

然后将这个fraction交给我们的插值器计算后得到新的fraction,再将新的fraction交给我们的估值算法,估值算法根据开始、结束、fraction得到当前属性(动画作用的属性)应该的值,最大调用反射进行设置;

start中还会根据动画的状态,如果没有结束,不断的调用scheduleAnimation();该方法内部利用mChoreographer不断的去重复我们的上述步骤。

好了,顺便说一句,在看源码的时候,一定要注意,你点进去的有可能不是真正运行时调用的,记得查看该方法子类,比如我们查看ObjectAnimator的方法,可能我们某个方法会跟到其父类ValueAnimator的方法,但是记得查看ObjectAnimator是否复写了该方法,如果复写了,你该看的应该是ObjectAnimator的方法。

标题基于SpringBoot+Vue的学生交流互助平台研究AI更换标题第1章引言介绍学生交流互助平台的研究背景、意义、现状、方法与创新点。1.1研究背景与意义分析学生交流互助平台在当前教育环境下的需求及其重要性。1.2国内外研究现状综述国内外在学生交流互助平台方面的研究进展与实践应用。1.3研究方法与创新点概述本研究采用的方法论、技术路线及预期的创新成果。第2章相关理论阐述SpringBoot与Vue框架的理论基础及在学生交流互助平台中的应用。2.1SpringBoot框架概述介绍SpringBoot框架的核心思想、特点及优势。2.2Vue框架概述阐述Vue框架的基本原理、组件化开发思想及与前端的交互机制。2.3SpringBoot与Vue的整合应用探讨SpringBoot与Vue在学生交流互助平台中的整合方式及优势。第3章平台需求分析深入分析学生交流互助平台的功能需求、非功能需求及用户体验要求。3.1功能需求分析详细阐述平台的各项功能需求,如用户管理、信息交流、互助学习等。3.2非功能需求分析对平台的性能、安全性、可扩展性等非功能需求进行分析。3.3用户体验要求从用户角度出发,提出平台在易用性、美观性等方面的要求。第4章平台设计与实现具体描述学生交流互助平台的架构设计、功能实现及前后端交互细节。4.1平台架构设计给出平台的整体架构设计,包括前后端分离、微服务架构等思想的应用。4.2功能模块实现详细阐述各个功能模块的实现过程,如用户登录注册、信息发布与查看、在线交流等。4.3前后端交互细节介绍前后端数据交互的方式、接口设计及数据传输过程中的安全问题。第5章平台测试与优化对平台进行全面的测试,发现并解决潜在问题,同时进行优化以提高性能。5.1测试环境与方案介绍测试环境的搭建及所采用的测试方案,包括单元测试、集成测试等。5.2测试结果分析对测试结果进行详细分析,找出问题的根源并
内容概要:本文详细介绍了一个基于灰狼优化算法(GWO)优化的卷积双向长短期记忆神经网络(CNN-BiLSTM)融合注意力机制的多变量多步时间序列预测项目。该项目旨在解决传统时序预测方法难以捕捉非线性、复杂时序依赖关系的问题,通过融合CNN的空间特征提取、BiLSTM的时序建模能力及注意力机制的动态权重调节能力,实现对多变量多步时间序列的精准预测。项目不仅涵盖了数据预处理、模型构建与训练、性能评估,还包括了GUI界面的设计与实现。此外,文章还讨论了模型的部署、应用领域及其未来改进方向。 适合人群:具备一定编程基础,特别是对深度学习、时间序列预测及优化算法有一定了解的研发人员和数据科学家。 使用场景及目标:①用于智能电网负荷预测、金融市场多资产价格预测、环境气象多参数预报、智能制造设备状态监测与预测维护、交通流量预测与智慧交通管理、医疗健康多指标预测等领域;②提升多变量多步时间序列预测精度,优化资源调度和风险管控;③实现自动化超参数优化,降低人工调参成本,提高模型训练效率;④增强模型对复杂时序数据特征的学习能力,促进智能决策支持应用。 阅读建议:此资源不仅提供了详细的代码实现和模型架构解析,还深入探讨了模型优化和实际应用中的挑战与解决方案。因此,在学习过程中,建议结合理论与实践,逐步理解各个模块的功能和实现细节,并尝试在自己的项目中应用这些技术和方法。同时,注意数据预处理的重要性,合理设置模型参数与网络结构,控制多步预测误差传播,防范过拟合,规划计算资源与训练时间,关注模型的可解释性和透明度,以及持续更新与迭代模型,以适应数据分布的变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值