最后start()。
好了,现在我想问个问题,根据上面这些参数,如果我要你设计个属性动画框架,你怎么做?
这个嘛,好整,拿到上述参数之后,start()中,开启一个定时器,去执行一个任务;在任务内部,根据Interpolator计算出来的fraction,交给Evaluator,得到属性当前应该设置的值,然后反射设置tagert的指定属性,ok,奏事这么简单。嗯,大体上应该就是这样,当然了,源码的实现肯定复杂很多,但是万变不离其宗,所以接下来的源码阅读,就是去验证我们的这个答案。
3、源码分析
======
好了,猜想完了,我们就得进入验证阶段了~~
那么,我们源码的入口就是上述代码了,不过貌似上述代码调用了好几个方法,but,我觉得start之前的代码,无法是初始化实例,设置一些成员变量。
首先我们看ofInt,这里为了简单,我们的ofInt中的values参数,默认就一个,类似 .ofInt(view, “translationX”, 300) ;
1、ofInt
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,简单看下
private ObjectAnimator(Object target, String propertyName) {
mTarget = target;
setPropertyName(propertyName);
}
public void setPropertyName(String propertyName) {
//…
mPropertyName = propertyName;
mInitialized = false;
}
记录完成target,propName以后,调用setIntValues
@Override
public void setIntValues(int… values) {
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
}
可以看到,把我们的propName,和values传入到了一个PropertyValuesHolder的ofInt方法中,去构造一个PropertyValuesHolder对象,这个对象是干什么的呢?
从字面上看,是保存view在动画期间的属性和值,记住是动画期间的。继续往下看:
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);去构造与保存的:
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) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-SAxN48eY-1712681706278)]
[外链图片转存中…(img-NZvtUXf3-1712681706279)]
[外链图片转存中…(img-mB1kNU4T-1712681706279)]
[外链图片转存中…(img-ugirKKA0-1712681706279)]
[外链图片转存中…(img-SF0gNUjo-1712681706279)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!