Android并发编程高级面试题汇总最全最细面试题讲解持续更新中👊👊
👀你想要的面试题这里都有👀
👇👇👇
属性动画的原理是怎么样的?
这道题想考察什么?
这道题想考察同学对 动画 的理解。
考生应该如何回答
属性动画的原理很简单,可以用一句话描述:最开始先设定好动画的基本属性信息,然后属性的数值按照设定逻辑变化并刷新视图(变化的属性会带来视图效果的变化),接着会判断动画是否达到结束条件,如果没有达到则重复数值的变化和视图刷新直到结束。
具体的细节我们可以看后面的分析。
属性动画可以分解为以下五个步骤:
- 设置动画的基本信息,包括运行时间、动画效果、开始值、结束值。
- 设置属性值的变化逻辑,包括插值器、估值器。
- 根据变化的逻辑不断的改变值
- 值改变后,就赋给对象的属性
- 调用invalidate刷新视图,并且判断当前的数值是否为结束值。如果当前值为结束值,则动画结束。如果当前值不等于结束值则重复第4步。
属性动画原理详解
属性动画的重要类
关键帧KeyFrame
关键帧KeyFrame的注释是"This class holds a time/value pair for an animation",它包含时间和数值两个属性,用来描述动画运行中多个画面中的一个。KeyFrame的时间属性描述了这个画面发生在整个动画的什么时刻,keyFrame的数值属性描述了当前时刻属性的值。
在属性动画的创建过程中,我们会设定开始值和结束值。在框架内部,开始值和结束值最终会转化成为关键帧KeyFrame。
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}
从上述代码过程中可以看到,参数values最终会转化为KeyFrame关键帧。这个里面会有两种情况,一种情况是values长度为一,另外一种是大于一。为一的情况,会生成开始帧和结束帧两个关键帧,开始帧数据都为0,结束帧的数据是传入的参数。不为一的情况,就是比较简单,values长度为多少就生成多少的关键帧。
PropertyValuesHolder
ObjectAnimator只能对单个属性进行操作,如果想实现比较复杂的效果就需要用到PropertyValuesHolder了。
PropertyValuesHolder的注释是"This class holds information about a property and the values that that property should take on during an animation",它包含了属性,以及该属性在动画运行期间的值。简单来讲就是,包含了属性名和多个关键帧。属性名表明了是哪个属性需要发生变化,关键帧表明动画该如何变化。

从上图看来,PropertyValuesHolder包含了属性名和多个关键帧。这里面属性是透明度,描述的是透明度的动画变化,在实际运用过程中可以换成其他属性。KeyFrame中的fraction代表的是时间、value表示数值,三个KeyFrame表示三个时间点的属性值。
动画基本信息设定
var objectAnimation: ObjectAnimator = ObjectAnimator.ofFloat(textView, "translationY", 0f, 400f)
objectAnimation.setDuration(5000)
objectAnimation.interpolator = DecelerateInterpolator()
objectAnimation.start()
如上面代码所示,我们设定了一个平移的动画。该动画作用于textview这个控件上,从0f的位置平移至400f的位置。动画的总共耗时5000ms,并且运用了DecelerateInterpolator动画变化速率越来越慢的插值器。这样一来我们把动画的目标控件、开始值、结束值、动画时间以及变化逻辑插值器都设定好了,包括了本章节开头提到的步骤一和步骤二的内容。
接下来我们研究一下代码内部的变化。首先通过ofFloat会生成一个ObjectAnimator对象,然后根据传入的属性值生成PropertyValuesHodler,接着根据传入的数值生成对应的关键帧,再设置相关的动画时间、插值器,整个动画的信息就设置完毕。
Start方法开启动画
调用start方法整个动画就开始运作起来,最终会进入了ValueAnimator的start方法。ValueAnimator#start方法中三件重要的事情:一是初始化估值器;二是计算属性值并设置到控件上;三是注册同步信号;
初始化估值器是通过调用startAnimation(),最后了会调用到initAnimation方法。在initAnimation方法里面会遍历PropertyValuesHolder,并逐个进行执行init。
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
其中mValues代表的是PropertyValuesHolder的集合,看完下面的代码,一切都很清晰明了了。
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
mKeyframes.setEvaluator(mEvaluator);
}
}
系统会判断当前是否有估值器对象,如果没有会根据mValueType的类型默认分配一个,接着就是简单的设置。
计算属性值是通过调用setCurrentPlayTime方法,最终会调用到animateValue()。在animateValue()中,会先通过插值器mInterpolator拿到当前动画的完成度,然后把完成度给到每一个PropertyValuesHolder用于计算动画的属性值。
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);
}
...
}
mValues[i].calculateValue最终会调用到KeyFrameSet的getValue方法,看完这个代码其实一切都很明了了。
public Object getValue(float fraction) {
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
mLastKeyframe.getValue());
}
...
for (int i = 1; i < mNumKeyframes; ++i) {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
...
}
上述代码中分了两种情况,一种情况是关键帧数目为二,一种不为二。为二的情况会传入完成度fraction、开始帧的值、结束帧的值给到估值器,由估值器最终计算数据。不为二的情况会通过fraction完成度寻找最合适的前帧prevKeyframe和后帧nextKeyframe,之后进行的内容就和关键帧为二的情况十分类似,通过完成度和前帧数值、后帧数值计算当前的属性值。
设置属性值的代码在Object的animateValue方法:
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
上面代码其实是简单的遍历PropertyValuesHolder,然后给每一个PropertyValuesHolder设置动画数值。属性动画是通过反射的方式设置数值的,在具体代码中表现是先拿取到属性set方法的Method对象,当需要使用的时候直接invoke。
void setAnimatedValue(Object target) {
...
if (mSetter != null) {
try {
mTmpValueArray[0] = mIntAnimatedValue;
//反射设置属性
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
...
}
当调用ValueAnimator的start方法的时候,则会开始注册同步服务信号。注册同步服务信号其实就是向系统请求刷新屏幕。ValueAnimator的start方法最终会调用到MyFrameCallbackProvider的postFrameCallback方法,如下所示:
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
mChoreographer是编舞者,管理app端向系统端申请同步服务信号的事情。当系统执行同步,也就是刷新的时候,会回调callback。我们来看下callback中的代码。
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
//判断动画是否结束
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
callback中的代码就只有两个内容:一个是执行doAnimationFrame,主要是执行新一轮的数值计算;另外一个是判断动画是否满足结束条件。
doAnimationFrame函数最终会调用到ValueAnimator#animateValue方法,其实也就是执行新一轮的动画的数值计算,先通过插值器、时间算出完成度,然后通过估值器、前后帧的值算出动画的数值,最后设置到控件上。
mAnimationCallbacks.size() > 0则说明动画未结束,继续申请一下次的同步服务信号,也就是需要继续刷新界面。不满足结束条件就继续刷新,执行回调的时候又判断是否需要继续刷新。类似于一种循环的方式,不断的推动动画的运行,直至满足条件动画结束。
总结
综上所述,你可以把动画的原理看中两个部分:一个部分与数据计算有关,另外一个部分与刷新界面有关。数据计算有关的部分,包括到关键帧、插值器、估值器、动画时间,它控制着动画运行过程中每个画面的数值。刷新界面有关就是不断的向系统申请刷新,当系统下发刷新的时候先运行动画的数据计算,然后判断动画是否结束。如果结束则不再想系统申请刷新,如果没有结束继续申请,直至满足条件结束。
由于面试题内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要2023年Android中高级最全面试真题答案 完整文档的可扫描下方卡片免费获取~

PS:(文末还有使用ChatGPT机器人小福利哦!!大家不要错过)
目录

第一章 Java方面
- Java基础部分
- Java集合
- Java多线程
- Java虚拟机

第二章 Android方面
- Android四大组件相关
- Android异步任务和消息机制
- Android UI绘制相关
- Android性能调优相关
- Android中的IPC
- Android系统SDK相关
- 第三方框架分析
- 综合技术
- 数据结构方面
- 设计模式
- 计算机网络方面
- Kotlin方面

第三章 音视频开发高频面试题
- 为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?
- 怎么做到直播秒开优化?
- 直方图在图像处理里面最重要的作用是什么?
- 数字图像滤波有哪些方法?
- 图像可以提取的特征有哪些?
- 衡量图像重建好坏的标准有哪些?怎样计算?

第四章 Flutter高频面试题
- Dart部分
- Flutter部分

第五章 算法高频面试题
- 如何高效寻找素数
- 如何运用二分查找算法
- 如何高效解决雨水问题
- 如何去除有序数组的重复元素
- 如何高效进行模幂运算
- 如何寻找最长回文子串

第六章 Andrio Framework方面
- 系统启动流程面试题解析
- Binder面试题解析
- Handler面试题解析
- AMS面试题解析

本文详细解读了Android属性动画的工作原理,涉及关键帧、PropertyValuesHolder、ObjectAnimator的使用,以及动画设置、计算和刷新过程。同时提及了并发编程在面试中的重要性。

被折叠的 条评论
为什么被折叠?



