Android面试题高级UI面试题——动画的分类以和区别属性动画的原理是怎么样的
一、动画的分类以及区别
这道题想考察什么?
- 是否了解安卓的动画?
考察的知识点
- 动画的分类
- 动画的区别
考生应该如何回答
动画的种类
首先回答下安卓的动画种类,一共分为三种
- 帧动画:由数张图片连续播放产生的动画效果。
- 补间动画:对View的平移,旋转,缩放,透明产生效果;
- 属性动画:动态的改变属性产生动画效果;
动画的区别
帧动画
帧动画其实就是简单的收集N张图片,然后依次显示这些图片。由于人眼"视觉暂留"的原因,会让我们造成动画的"错觉"。视觉暂留是指我们看到的画面,会在大脑中停留短暂的时间而不立即消失。
帧动画的特点:帧动画不会改变控件的属性,只是通过播放图片来达到动画效果,制作简单但效果单一,且占用空间较大。
补间动画
补间动画会先声明好两个关键帧,开始帧和结束帧。开始帧是用来描述动画开始时的状态,结束帧是用来描述动画结束时的状态,而动画中间如何由开始帧演变到结束帧是由系统计算而来。
补间动画的特点:安卓中补间动画只改变了View的显示效果,而未改变View的真正属性值。比如说,我们使用补间动画放大View的宽度,在动画运行中View宽度变大了,但仅仅只是显示效果上宽度变大了,并没有真正改变View宽度的值。
属性动画
属性动画与其他动画最大的不同是它会不断的更新View的属性值,从而产生动画效果。它不仅仅显示效果发生了改变,View的属性值也发生了变化。
属性动画和补间动画一样会声明关键帧,关键帧与关键帧之间的属性变化由系统计算而来。它们之间有个小的不同点是,属性动画可以声明一个、两个或者多个关键帧,而补间动画固定是开始帧和结束帧。
二、属性动画的原理是怎么样的?
这道题想考察什么?
这道题想考察同学对 动画 的理解。
考生应该如何回答
属性动画的原理很简单,可以用一句话描述:最开始先设定好动画的基本属性信息,然后属性的数值按照设定逻辑变化并刷新视图(变化的属性会带来视图效果的变化),接着会判断动画是否达到结束条件,如果没有达到则重复数值的变化和视图刷新直到结束。
具体的细节我们可以看后面的分析。
属性动画可以分解为以下五个步骤:
- 设置动画的基本信息,包括运行时间、动画效果、开始值、结束值。
- 设置属性值的变化逻辑,包括插值器、估值器。
- 根据变化的逻辑不断的改变值
- 值改变后,就赋给对象的属性
- 调用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则说明动画未结束,继续申请一下次的同步服务信号,也就是需要继续刷新界面。不满足结束条件就继续刷新,执行回调的时候又判断是否需要继续刷新。类似于一种循环的方式,不断的推动动画的运行,直至满足条件动画结束。
总结
综上所述,你可以把动画的原理看中两个部分:一个部分与数据计算有关,另外一个部分与刷新界面有关。数据计算有关的部分,包括到关键帧、插值器、估值器、动画时间,它控制着动画运行过程中每个画面的数值。刷新界面有关就是不断的向系统申请刷新,当系统下发刷新的时候先运行动画的数据计算,然后判断动画是否结束。如果结束则不再想系统申请刷新,如果没有结束继续申请,直至满足条件结束。
详细关注公众号:Android老皮
还能解锁 《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版
内容如下:
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgiiffa
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13