动画相关面试总结:Android 动画相关面试总结
动画分类
Android动画分为视图动画和属性动画
动画系列文章:动画入门和进阶文章
视图动画
包括:逐帧动画(frame-by-frame animation)、补间动画(tweened animation)
作用:能为View对象添加动画效果。
优点:使用简单,并且设置需要的时间较短。
缺点:
只能对公开对象的部分添加动画效果(例如:可以对View缩放和旋转添加动画,但无法对背景颜色这样做);
只能对绘制视图的位置进行修改,而不能修改实际的视图本身(例如:View位移到了上方,但是其点击区域还是在原来的地方)。
属性动画
包括:ValueAnimator、ObjectAnimator 、AnimatorSet
作用:可以为任何对象(视图和非视图)的任何属性添加动画效果,也可以产生动画过程的数据。
优点:相比视图动画,使用相对复杂,设置需要的时间更长。
缺点:
可以为任何对象(视图和非视图)的任何属性添加动画效果;
能够修改实际对象本身。
视图动画与属性动画的一些区别
视图动画继承自Animation
,在android.view.animation
包中。
Animation
内部是没有估值器(Evaluator)的,只有插值器(Interpolator)!子类也没有估值器的踪影
属性动画继承自Animator
,在android.animation
包中。
ValueAnimator
有插值器(Interpolator),并且数据单元PropertyValuesHolder
中会有估值器(Evaluator)的设置。
AnimatorSet
也是继承自Animator
,所有AnimatorSet
是属于属性动画。
为什么视图动画只能修改视图而无法改变自身属性?
//启动方式1 ===========================
view.startAnimation(animation);
//View#startAnimation
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME); //Animation.START_ON_FIRST_FRAME就是-1
setAnimation(animation);
invalidateParentCaches();
invalidate(true); //让view重绘
}
//启动方式2 ===========================
view.setAnimation(animation);
animation.start();
//Animation#start:没设置开始动画的时间,需要等到下次view重绘
public void start() {
setStartTime(-1);
}
//Animation#startNow:设置了开始时间,能非常快就执行动画(相当于立即执行动画)
public void startNow() {
setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
//两种启动方式其实都没啥区别。都是设置给view设置了animation之后,就等待动画执行。动画并不会马上执行的,需要等到view的下次重绘才会执行。
view#draw() -> view#applyLegacyAnimation() -> animation#getTransformation() -> animation#applyTransformation()
//View#draw
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
...
}
...
if (more && hardwareAcceleratedCanvas) {
if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
//还有动画继续通知
invalidate(true);
}
}
...
return more;
}
//View#applyLegacyAnimation
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
...
final boolean initialized = a.isInitialized();
if (!initialized) {
...
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
...
return more;
}
//Animation#applyLegacyAnimation
public boolean getTransformation(long currentTime, Transformation outTransformation,
float scale) {
mScaleFactor = scale;
return getTransformation(currentTime, outTransformation);
}
//Animation#getTransformation
public boolean getTransformation(long currentTime, Transformation outTransformation) {
...
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
...
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation); //依靠子类重写该方法,实现具体逻辑
}
...
return mMore;
}
//AlphaAnimation#applyTransformation
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
/* 总结:
绘制子view都会先对画布状态进行保存save(),绘制完后又会恢复restore(),所以一个view的绘制不会影响另外一个子view的绘制。但如果该view是viewgroup,会影响到其所有的子view的绘制。
动画是通过父view来不断调整子view的画布canvas坐标系来实现的,发生动画的其实是父View而不是该view。所以 补间动画其实只是调整了子view画布canvas的坐标系,其实并没有修改任何属性,所以只能在原位置才能处理触摸事件。
当view调用了 View.startAnimation() 时动画并没有马上就执行,会触发遍历view树的绘制,
调用到 View 的 draw() 方法,如果 View 有绑定动画,那么会去调用applyLegacyAnimation(),
内部调用 getTransformation() 来根据当前时间计算动画进度,紧接着调用 applyTransformation() 并传入动画进度来应用动画。
getTransformation() 会返回动画是否执行完成的状态, applyLegacyAnimation() 会根据 getTransformation() 的返回值
来决定是否通知 ViewRootImpl 再发起一次遍历请求,遍历 View 树绘制,重复上面的步骤,直到动画结束。
*/
视图动画介绍
帧动画
由N张静态图片收集通过轮播,让人看感觉像是会动一样。
AnimationDrawable - 帧动画的使用
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/ic_play_pink_00" android:duration="50" />
<item android:drawable="@drawable/ic_play_pink_01" android:duration="50" />
<item android:drawable="@drawable/ic_play_pink_02" android:duration="50" />
<item android:drawable="@drawable/ic_play_pink_03" android:duration="50" />
<item android:drawable="@drawable/ic_play_pink_04" android:duration="50" />
</animation-list>
oneshot:true:只播放一次;false:循环播放。
AnimationDrawable drawable = (AnimationDrawable) mContext.getResources().getDrawable(R.drawable.anim_living);
drawable.start();
imageView.setImageDrawable(drawable);
帧动画优化 - 避免oom
在帧动画资源特别多的情况下,此时用AnimationDrawable就不是那么理想了。因为AnimationDrawable使用一个Drawable数组来存储每一帧的图像,会直接把全部图片加载进内存。在内存紧张的情况下会出现卡顿甚至OOM。
减少帧数、压缩图片
降低帧动画的帧数,别搞那么高帧率了,能用就行。
将所有资源图片压缩一波,什么png转webp统统走一遍。
如果播放完了给View来一波stopAnimation()、setDrawable(null)等。
bitmap优化
http://stackoverflow.com/questions/8692328/causing-outofmemoryerror-in-frame-by-frame-animation-in-android
surfaceView
https://blog.csdn.net/qq_25804863/article/details/65634864
https://www.jianshu.com/p/edcca8d3dd00
补间动画
补间动画开发者只需指定动画开始,以及动画结束"关键帧", 而动画变化的"中间帧"则由系统计算并补齐
补间动画类型
-
AlphaAnimation:透明度渐变效果
对应xml标签
<alpha>
-
ScaleAnimation:缩放渐变效果
对应xml标签
<scale>
-
TranslateAnimation:位移渐变效果
对应xml标签
<translate>
-
RotateAnimation:旋转渐变效果
对应xml标签
<rotate>
-
AnimationSet:组合渐变,就是前面多种渐变的组合
对应xml标签
<set>
插值器 - Interpolator
插值器是用来控制动画变化的速率。
android提供的插值器
- LinearInterpolator:动画以均匀的速度改变
- AccelerateInterpolator:在动画开始的地方改变速度较慢,然后开始加速
- AccelerateDecelerateInterpolator:在动画开始、结束的地方改变速度较慢,中间时加速
- CycleInterpolator:动画循环播放特定次数,变化速度按正弦曲线改变: Math.sin(2 * mCycles * Math.PI * input)
- DecelerateInterpolator:在动画开始的地方改变速度较快,然后开始减速
- AnticipateInterpolator:反向,先向相反方向改变一段再加速播放
- AnticipateOvershootInterpolator:开始的时候向后然后向前甩一定值后返回最后的值
- BounceInterpolator: 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
- OvershottInterpolator:回弹,最后超出目的值然后缓慢改变到目的值
当然我们也可以通过实现Interpolator 接口来自定义插值器。
自定义Interpolator的步骤:
Step1:实现Interpolator接口,重写getInterpolation方法;
Step2:在getInterpolation方法利用参数input得到动画进度,通过input计算出你想要的进度并返回。
//如系统提供的加速插值器
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
//重点,如果按照默认值,动画的完成状态的变化是动画进度变化(运行时间)的平方。Input=0.1 返回0.01,input=0.9 返回0.81,input=1 返回1
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
}
属性动画介绍
ValueAnimator
ValueAnimator是属性动画中重要且最基本的类。ValueAnimator直接子类有两个ObjectAnimator
和TimeAnimator
。
ValueAnimator只对数值进行计算。要想实现动画,需要监听值变化的回调来获取数值,最后操作于目标对象身上。
ValueAnimator提供的创建方法:
ValueAnimator.ofInt(int … values)//处理整形参数
ValueAnimator.ofFloat(float … values)//处理浮点型
ValueAnimator. ofArgb(int… values) //处理颜色
ValueAnimator.ofObject(TypeEvaluator evaluator, Object… values)//处理object对象,需要自定义估值器
ValueAnimator.ofPropertyValuesHolder(PropertyValuesHolder… values) //处理PropertyValuesHolder
ValueAnimator的使用
//Step1:利用上面的函数生成ValueAnimator对象
ValueAnimator v = ValueAnimator.ofFloat(0, 1);
v.setDuration(1000);
//Step2:设置动画的监听 addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
v.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//Step3:利用监听函数获取当前动画的值getAnimatedValue(),设置给View实现动画
float f = (float) animation.getAnimatedValue();
view.setAlpha(f);
}
});
v.start();
估值器 - Evaluator
估值器是用来计算出属性在当前进度的具体值。也就是说插值器是计算进度百分比,而估值器是利用这个百分比,计算出具体值。
每个属性动画都必须有估值器。
//ValueAnimator#ofArgb - 就有ArgbEvaluator
public static ValueAnimator ofArgb(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
anim.setEvaluator(ArgbEvaluator.getInstance());
return anim;
}
//那为什么ofInt跟ofFloat就没有设置呢?
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
//PropertyValuesHolder#init 因为int/float的估值器在创建PropertyValuesHolder的时候就设置了
void init() {
if (mEvaluator == null) {
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null;
}
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
自定义估值器如果使用步骤:
Step1:实现TypeEvaluator接口,重写evaluate方法;
Step2:根据evaluate方法提供的参数计算出具体值。
//IntEvaluator.java
public class IntEvaluator implements TypeEvaluator<Integer> {
// 参数说明
// fraction:插值器getInterpolation()的返回值
// startValue:动画的初始值
// endValue:动画的结束值
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
属性动画数据存储单元 - PropertyValuesHolder
PropertyValuesHolder是属性动画的数据存储单元。是用来保存某个属性property
对应的一组值,这些值对应了一个动画周期中的所有关键帧。
在属性动画中,不管我们调用哪个ofXXX,最终都会被封装成PropertyValuesHolder,然后保存在ValueAnimator的mValues中。
同时利用PropertyValuesHolder也可以在属性动画中实现类似AnimatorSet的多动画执行的功能。具体可以使用ofPropertyValuesHolder方法。
PropertyValuesHolder的使用:
//可以通过Keyframe类实现添加关键帧数据。
Keyframe ka0 = Keyframe.ofFloat(0f, 0f);
Keyframe ka1 = Keyframe.ofFloat(1f, 1f);
PropertyValuesHolder p0 = PropertyValuesHolder.ofKeyframe("alpha", ka0, ka1);
Keyframe ks0 = Keyframe.ofFloat(0f, 0.2f);
Keyframe ks1 = Keyframe.ofFloat(1f, 1.0f);
PropertyValuesHolder p1 = PropertyValuesHolder.ofKeyframe("scaleX", ks0, ks1);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(view, p0, p1);
objectAnimator.setDuration(2500);
objectAnimator.start();
ObjectAnimator
由于ValueAnimator只能对数值进行计算,如果要实现动画还需要设置监听器,在回调函数内手动给对象设值。这样使用起来十分不友好。由此诞生了ObjectAnimator。
ObjectAnimator继承自ValueAnimator,在构建ObjectAnimator的时候就可以传入目标对象,让ObjectAnimator帮我们完成动画,简化了我们的代码实现。
ObjectAnimator提供的创建方法:
ofArgb(Object target, String propertyName, int... values)
ofArgb(T target, Property<T, Integer> property, int... values)
ofFloat(Object target, String xPropertyName, String yPropertyName, Path path)
ofFloat(T target, Property<T, Float> property, float... values)
ofFloat(T target, Property<T, Float> xProperty, Property<T, Float> yProperty, Path path)
ofFloat(Object target, String propertyName, float... values)
ofInt(T target, Property<T, Integer> xProperty, Property<T, Integer> yProperty, Path path)
ofInt(T target, Property<T, Integer> property, int... values)
ofInt(Object target, String propertyName, int... values)
ofInt(Object target, String xPropertyName, String yPropertyName, Path path)
ofMultiFloat(Object target, String propertyName, float[][] values)
ofMultiFloat(Object target, String propertyName, Path path)
ofMultiFloat(Object target, String propertyName, TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values)
ofMultiInt(Object target, String propertyName, int[][] values)
ofMultiInt(Object target, String propertyName, Path path)
ofMultiInt(Object target, String propertyName, TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values)
ofObject(T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values)
ofObject(Object target, String propertyName, TypeConverter<PointF, ?> converter, Path path)
ofObject(T target, Property<T, V> property, TypeConverter<PointF, V> converter, Path path)
ofObject(T target, Property<T, P> property, TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values)
ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)
ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)
ObjectAnimator的使用
//给view设置透明度
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 0.1f,1.0f);
objectAnimator.setDuration(1000);
objectAnimator.setInterpolator(new LinearInterpolator());
ObjectAnimator的注意
1、必须保证目标对象有对应的属性,并且需要提供该属性的setter方法。ObjectAnimator是靠反射来给目标对象设值的。
如 "alpha" -> 会反射 setAlpha(系统会帮你强制首字母大写之后加set,再进行反射)
2、如果value…只设置了一个值,需要提供属性的setter方法。ObjectAnimator会认为这是结束值,并且会反射属性的getter方法,获取属性的初始值作为开始值。
3、目标对象的属性必须跟ObjectAnimator.ofXXX的类型相同
4、ObjectAnimator内部会主动调用目标对象的setter方法,但是这并不会导致view的重绘,需要你在setter方法中主动调用invalidate方法。
或者设置动画监听函数在onAnimationUpdate的回调中调用invalidate方法。如果你要自己定义对象的setter方法,就要主动调用invalidate方法。
AnimatorSet
AnimatorSet可以指定一组动画的执行顺序,让它们可以一起执行,顺序执行,延迟执行。
https://blog.csdn.net/u010126792/article/details/85683405
View的属性动画类 - ViewPropertyAnimator
ViewPropertyAnimator内部依然是ValueAnimator实现动画。
ViewPropertyAnimator的基本函数:
setDuration(); //设置动画时长
setInterpolator(); //设置插值器
setStartDelay(); //设置延迟开始时间
start(); //立刻开始动画
cancel(); //取消动画
ViewPropertyAnimator的使用:
//使用非常简单,直接在View#animate()
view.animate()
.translationX(10)
.setDuration(1000)
.start();