Android 属性动画 源码解析 深入了解其内部实现

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);去构造与保存的:

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();

mKeyframes.addAll(Arrays.asList(keyframes));

mFirstKeyframe = mKeyframes.get(0);

mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);

mInterpolator = mLastKeyframe.getInterpolator();

}

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

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

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内部的一些成员变量。

public IntKeyframeSet(IntKeyframe… keyframes) {

mNumKeyframes = keyframes.length;

mKeyframes = new ArrayList();

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方法。

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等等。

好了,接下来会轻松点,按照顺序到setInterpolator了:

2、setInterpolator


@Override

public void setInterpolator(TimeInterpolator value) {

if (value != null) {

mInterpolator = value;

} else {

mInterpolator = new LinearInterpolator();

}

}

没撒说的,记录下插值器,我们这里也线性插值器,默认也是~~

然后是setEvaluator。

3、setEvaluator


public void setEvaluator(TypeEvaluator value) {

if (value != null && mValues != null && mValues.length > 0) {

mValues[0].setEvaluator(value);

}

}

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

public void setEvaluator(TypeEvaluator evaluator) {

mEvaluator = evaluator;

mKeyframeSet.setEvaluator(evaluator);

}

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

public void setEvaluator(TypeEvaluator evaluator) {

mEvaluator = evaluator;

}

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

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

4、setDuration


// 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


喝杯水,小憩一下,准备征战start()方法。

@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)

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;

void initAnimation() {

if (!mInitialized) {

int numValues = mValues.length;

for (int i = 0; i < numValues; ++i) {

mValues[i].init();

}

mInitialized = true;

}

PropertyValuesHolder的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

mKeyframeSet.setEvaluator(mEvaluator);

}

}

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

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

final boolean doAnimationFrame(long frameTime) {

final long currentTime = Math.max(frameTime, mStartTime);

return animationFrame(currentTime);

}

内部调用了:animationFrame(currentTime);

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方法:

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方法:

void calculateValue(float fraction) {

mAnimatedValue = mKeyframeSet.getValue(fraction);

}

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

@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初始化的时候就设置了,至于该属性应该设置的值,上面有一句:“ 好了,for循环结束了,经过我们插值器和估值算法得出的值,最终给了IntPropertyValueHolder的mIntAnimatedValue属性 ” 。是不是全了~~反射的代码就不贴了。

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

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

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

/**

  • 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方法。

public void run() {

mAnimationScheduled = false;

doAnimationFrame(mChoreographer.getFrameTime());

}

private void doAnimationFrame(long frameTime) {

while (mPendingAnimations.size() > 0) {

ArrayList pendingCopy =

(ArrayList) 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()

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

想要了解更多关于大厂面试的同学可以点赞支持一下,除此之外,我也分享一些优质资源,包括:Android学习PDF+架构视频+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

厂,18年进入阿里一直到现在。**

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-s78nEnkr-1713755172176)]

[外链图片转存中…(img-1Dnwu1ey-1713755172178)]

[外链图片转存中…(img-uQPy5yEg-1713755172179)]

[外链图片转存中…(img-48SXO0Nt-1713755172180)]

[外链图片转存中…(img-ZaZojG3w-1713755172181)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

[外链图片转存中…(img-ntIFJahB-1713755172183)]

最后

想要了解更多关于大厂面试的同学可以点赞支持一下,除此之外,我也分享一些优质资源,包括:Android学习PDF+架构视频+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值