Android 动画机制总结与代码
一、核心知识点总结
1. 动画分类
- View 动画(补间动画):包括平移、缩放、旋转、透明度,仅改变显示位置,不改变实际属性和响应区域。
- 帧动画:通过
AnimationDrawable 实现,本质是 Drawable 资源序列,易引发 OOM。
- 属性动画:可作用于任意对象的任意属性,真正改变属性值,支持响应区域更新。
2. View 动画特点
- 定义在
res/anim/
- 使用
AnimationUtils.loadAnimation() 加载 XML 动画
- 通过
startAnimation() 启动,clearAnimation() 取消
- 无法改变 View 的实际宽高、点击区域等
3. 属性动画核心类
ObjectAnimator:直接操作对象属性(需有对应 setter)
ValueAnimator:仅计算值变化,需配合 UpdateListener 手动赋值
AnimatorSet:组合多个动画
- 定义在
res/animator/
4. 插值器(Interpolator)与估值器(Evaluator)
- 插值器:根据时间流逝百分比计算属性变化百分比(如
LinearInterpolator、DecelerateInterpolator)
- 估值器:根据插值器返回的百分比计算实际属性值(如
FloatEvaluator、ArgbEvaluator)
5. 特殊场景处理
- 对
width 等非直接支持属性做动画:使用包装类(Wrapper)提供 getter/setter
- Activity 切换动画:
overridePendingTransition()
- 避免内存泄漏:在 Activity 结束时取消动画
二、代码文件(带完整注释)
AnimActivity.kt
package com.shakespace.artofandroid.chapter07anim
import android.animation.ObjectAnimator
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.R
import com.shakespace.firstlinecode.global.start
import kotlinx.android.synthetic.main.activity_anim.*
/**
* 动画主入口 Activity
*/
class AnimActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_anim)
// 跳转到 View 动画演示页
tv_animation.setOnClickListener {
start(AnimationActivity::class.java)
overridePendingTransition(R.anim.act_enter, R.anim.act_exit)
}
// 跳转到属性动画演示页
tv_animator.setOnClickListener {
start(AnimatorActivity::class.java)
overridePendingTransition(R.anim.act_enter, R.anim.act_exit)
}
// 演示如何对 width 做属性动画(使用包装类)
tv_change_width.setOnClickListener {
ObjectAnimator.ofInt(ViewWrapper(tv_demo), "width", 500).start()
}
}
/**
* 包装 View,为 width 提供 getter/setter
* 解决属性动画无法直接操作 layoutParam 的问题
*/
inner class ViewWrapper(private val view: View) {
fun getWidth(): Int {
return view.layoutParams.width
}
fun setWidth(width: Int) {
view.layoutParams.width = width
view.requestLayout()
}
}
}
AnimationActivity.kt
package com.shakespace.artofandroid.chapter07anim
import android.graphics.drawable.AnimationDrawable
import android.os.Bundle
import android.util.Log
import android.view.animation.*
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.R
import com.shakespace.firstlinecode.global.TAG
import kotlinx.android.synthetic.main.activity_animation.*
/**
* View 动画(补间动画 + 帧动画)演示
*/
class AnimationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_animation)
// 透明度动画
val alpha = AlphaAnimation(1f, 0f).apply {
duration = 2000
repeatCount = -1
repeatMode = Animation.REVERSE
}
// 旋转动画(以自身中心为轴)
val rotate = RotateAnimation(
-90f, 270f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f
).apply {
duration = 1000
repeatCount = -1
repeatMode = Animation.REVERSE
}
// 缩放动画
val scale = ScaleAnimation(
0.5f, 2f, 0.2f, 3f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
).apply {
duration = 1000
repeatCount = -1
repeatMode = Animation.REVERSE
}
// 平移动画
val trans = TranslateAnimation(
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 3f,
TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_SELF, 3f
).apply {
duration = 1000
repeatCount = -1
repeatMode = Animation.REVERSE
}
// 绑定点击事件
tv_alpha.setOnClickListener { iv_anim.startAnimation(alpha) }
tv_rotate.setOnClickListener { iv_anim.startAnimation(rotate) }
tv_scale.setOnClickListener { iv_anim.startAnimation(scale) }
tv_translation.setOnClickListener { iv_anim.startAnimation(trans) }
// 组合动画
tv_complex.setOnClickListener {
val set = AnimationSet(false).apply {
addAnimation(alpha)
addAnimation(rotate)
addAnimation(scale)
addAnimation(trans)
duration = 3000
}
iv_anim.startAnimation(set)
}
// 动画监听
trans.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
Log.e(TAG, "onAnimationRepeat: ")
}
override fun onAnimationEnd(animation: Animation?) {
Log.e(TAG, "onAnimationEnd: ")
}
override fun onAnimationStart(animation: Animation?) {
Log.e(TAG, "onAnimationStart: ")
}
})
// 从 XML 加载动画
tv_xml.setOnClickListener {
iv_anim.startAnimation(AnimationUtils.loadAnimation(this, R.anim.alpha))
}
// 帧动画(需在 drawable 中定义)
tv_frame.setOnClickListener {
val animationDrawable = tv_frame.background as AnimationDrawable
animationDrawable.start()
}
}
override fun onBackPressed() {
super.onBackPressed()
overridePendingTransition(R.anim.act_back_enter, R.anim.act_back_exit)
}
}
AnimatorActivity.kt
package com.shakespace.artofandroid.chapter07anim
import android.animation.*
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.R
import com.shakespace.firstlinecode.global.TAG
import kotlinx.android.synthetic.main.activity_animator.*
/**
* 属性动画演示
*/
class AnimatorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_animator)
// 属性动画:alpha
val alphaAnimator = ObjectAnimator.ofFloat(iv_anim, "alpha", 0.5f)
// 旋转动画
val rotationAnimator = ObjectAnimator.ofFloat(iv_anim, "rotation", 0f, 180f, 270f, 90f)
// 平移动画
val transAnimator = ObjectAnimator.ofFloat(iv_anim, "translationX", 500f, -200f, 100f, 0f)
// 缩放动画
val scaleAnimator = ObjectAnimator.ofFloat(iv_anim, "scaleY", 0.2f, 2f)
// 自定义插值器与估值器
rotationAnimator.setEvaluator(MyFloatEvaluator())
rotationAnimator.interpolator = MyDecelerateInterpolator()
// 绑定点击事件
tv_object_alpha.setOnClickListener {
alphaAnimator.duration = 2000
alphaAnimator.start()
}
tv_object_rotate.setOnClickListener {
rotationAnimator.duration = 1600
rotationAnimator.start()
}
tv_object_scale.setOnClickListener {
scaleAnimator.duration = 2000
scaleAnimator.start()
}
tv_object_trans.setOnClickListener {
transAnimator.duration = 2000
transAnimator.start()
}
// 动画集合
tv_object_set.setOnClickListener {
AnimatorSet().apply {
play(alphaAnimator).with(transAnimator).with(scaleAnimator).after(rotationAnimator)
duration = 2000
start()
}
}
// 动画监听(完整)
alphaAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {}
override fun onAnimationEnd(animation: Animator?) { Log.e(TAG, "onAnimationEnd: ") }
override fun onAnimationCancel(animation: Animator?) { Log.e(TAG, "onAnimationCancel: ") }
override fun onAnimationStart(animation: Animator?) { Log.e(TAG, "onAnimationStart: ") }
})
// 动画监听(简化版)
transAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
Log.e(TAG, "onAnimationEnd: ")
}
})
// 更新监听(每帧回调)
rotationAnimator.addUpdateListener {
Log.e(TAG, "onUpdate: ${it.animatedValue}")
}
// 从 XML 加载属性动画
tv_object_xml.setOnClickListener {
AnimatorInflater.loadAnimator(this, R.animator.demo).apply {
setTarget(iv_anim)
start()
}
}
iv_anim.setOnClickListener {
Log.e(TAG, "onClick: ")
}
}
override fun onBackPressed() {
super.onBackPressed()
overridePendingTransition(R.anim.act_back_enter, R.anim.act_back_exit)
}
}
MyDecelerateInterpolator.kt
package com.shakespace.artofandroid.chapter07anim
import android.util.Log
import android.view.animation.DecelerateInterpolator
import com.shakespace.firstlinecode.global.TAG
class MyDecelerateInterpolator : DecelerateInterpolator() {
override fun getInterpolation(input: Float): Float {
Log.e(TAG, "getInterpolation: ----$input ")
return super.getInterpolation(input)
}
}
MyFloatEvaluator.kt
package com.shakespace.artofandroid.chapter07anim
import android.animation.FloatEvaluator
import android.util.Log
import com.shakespace.firstlinecode.global.TAG
class MyFloatEvaluator : FloatEvaluator() {
override fun evaluate(fraction: Float, startValue: Number?, endValue: Number?): Float {
Log.e(TAG, "evaluate: fraction = $fraction startValue = $startValue endValue = $endValue")
return super.evaluate(fraction, startValue, endValue)
}
}
MyLinearInterpolator.kt
package com.shakespace.artofandroid.chapter07anim
import android.util.Log
import android.view.animation.LinearInterpolator
import com.shakespace.firstlinecode.global.TAG
class MyLinearInterpolator : LinearInterpolator() {
override fun getInterpolation(input: Float): Float {
Log.e(TAG, "getInterpolation: $input ")
return super.getInterpolation(input)
}
}
Rotate3DAnimation.kt
package com.shakespace.artofandroid.chapter07anim
import android.graphics.Camera
import android.view.animation.Animation
import android.view.animation.Transformation
/**
* 自定义 3D 旋转动画(围绕 Y 轴)
*/
class Rotate3DAnimation(
var fromDegree: Float,
val toDegree: Float,
val centerX: Float,
val centerY: Float,
val depthZ: Float,
val reverse: Boolean
) : Animation() {
var camera: Camera? = null
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val degrees = fromDegree + (toDegree - fromDegree) * interpolatedTime
val matrix = t?.matrix
camera?.apply {
save()
if (reverse) {
translate(0.0f, 0.0f, depthZ * interpolatedTime)
} else {
translate(0.0f, 0.0f, depthZ * (1.0f - interpolatedTime))
}
rotateY(degrees)
getMatrix(matrix)
restore()
matrix?.preTranslate(-centerX, -centerY)
matrix?.postTranslate(centerX, centerY)
}
}
override fun initialize(width: Int, height: Int, parentWidth: Int, parentHeight: Int) {
super.initialize(width, height, parentWidth, parentHeight)
camera = Camera()
}
}
三、文本笔记内容(原样保留)
z_memo_01viewAnim.txt
分类
1.View动画, 帧动画、属性动画
2.View动画
2.1 针对View
2.2 平移、缩放、旋转、透明度
2.3 对应4个类
TranslateAnimation
ScaleAnimation
RotateAnimation
AlphaAnimation
2.4 可以用代码创建,也可以用xml创建,建议使用xml,可读性好。
3. View动画 xml路径 -- res/anim/filename.xml
4. 代码中定义
// 以自身中心为参照
val rotate = RotateAnimation(
-90f,
270f,
RotateAnimation.RELATIVE_TO_SELF,
0.5f,
RotateAnimation.RELATIVE_TO_SELF,
0.5f
).apply {
duration = 1000
repeatCount = -1
repeatMode = Animation.REVERSE
}
tv_translation.setOnClickListener {
iv_anim.startAnimation(trans)
}
5. 使用xml中定义的动画
关键是 AnimationUtils.loadAnimation
tv_xml.setOnClickListener {
iv_anim.startAnimation(AnimationUtils.loadAnimation(this, R.anim.alpha))
}
6. 取消进行中的动画
view.clearAnimation()
Animation类的方法 解释
reset() 重置Animation的初始化
cancel() 取消Animation动画
start() 开始Animation动画
setAnimationListener() 给当前Animation设置动画监听
hasStarted() 判断当前Animation是否开始
hasEnded() 判断当前Animation是否结束
xml属性 java方法 解释
android:duration setDuration(long) 动画持续时间,毫秒为单位
android:ShareInterpolator setInterpolator(Interpolator) 设定插值器(指定的动画效果,譬如回弹等)
android:fillAfter setFillAfter(boolean) 控件动画结束时是否保持动画最后的状态
android:fillBefore setFillBefore(boolean) 控件动画结束时是否还原到开始动画前的状态
android:repeatMode setRepeatMode(int) 重复类型有两个值,reverse表示倒序回放,restart表示从头播放
android:startOffset setStartOffset(long) 调用start函数之后等待开始运行的时间,单位为毫秒
z_memo_02frameAnim.txt
分类
1.View动画, 帧动画、属性动画
2. 帧动画通过AnimationDrawable实现
3. 这是一个drawable,所以需要在drawable下创建资源
z_memo_03ElseApply.txt
1. 通过layoutAnimation 给ViewGroup加动画
参加 anim_layout.xml
2. 给Activity切换添加动画
overridePendingTransition(R.anim.act_enter,R.anim.act_exit)
第一个参数是给即将显示的Activity,后一个是当前。
3. 在fragmentTransaction中可以通过setCustomAnimations 设置fragment的进出场动画
4. 可以在主题中设置
<style name="AnimationActivity">
<item name="android:activityOpenEnterAnimation">@anim/alpha</item>
<item name="android:activityOpenExitAnimation">@anim/alpha</item>
<item name="android:activityCloseEnterAnimation">@anim/alpha</item>
<item name="android:activityCloseExitAnimation">@anim/alpha</item>
</style>
属性动画相比View动画存在一个缺陷,View动画改变的只是View的显示,而没有改变View的响应区域,并且View动画只能对View做四种类型的补间动画
z_memo_04propertyAnim.txt
View动画即补间动画
View动画存在一个缺陷,View动画改变的只是View的显示,而没有改变View的响应区域,并且View动画只能对View做四种类型的补间动画
补间动画只能进行简单动画,如果要执行一个颜色变化的动画,补间动画就做不到。
属性动画可以对任意对象进行动画,默认时间间隔300ms,帧率10ms/帧。
可以在一个时间间隔内完成一个属性值的变化。
常用的是ObjectAnimator, ValueAnimator,AnimatorSet
object是value的子类,更为常用。
它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性。
属性动画定义在
res/animator下
补间动画
res/anim
1. 一般用法
val anim = ValueAnimator.ofFloat(0f, 1f)
anim.duration = 700
anim.interpolator = LinearInterpolator()
anim.addUpdateListener { animation ->
// 监听变化 执行操作
mPhaseY = animation.animatedValue as Float
invalidate()
}
anim.start()
valueAnimator的常用方法就是ValueAnimator.ofFloat 和 ofInt
1. 一个针对小数,一个针对整数。
2. 都可以传入多个值
3. 此外还有一个ofObject
2. xml
val animator = AnimatorInflater.loadAnimator(this, R.animator.demo)
animator.setTarget(iv_anim)
animator.start()
3. else
repeatCount : 循环次数,-1 (INFINITE) ,无限循环
repeatMode: repeat、reverse
4. 实际使用属性动画时一般使用代码完成的,因为很多属性值范围运行时才知道
5. 针对Color变化的动画,需要设置Argb估值器
z_memo_05Interpolator.txt
TimeInterpolator
是个接口,时间插值器。
作用是根据时间流逝的百分比计算当前属性改变的百分比。
Interpolator 继承于 TimeInterpolator
LinearInterpolator
AccelerateDecelerateInterpolator
DecelerateInterpolator
插值器的效果主要看
getInterpolation 的返回值。
LinearInterpolator
public float getInterpolation(float input) {
return input;
}
输入等于输出,也就是说,假如时间进行了20%,属性变化也应该是20%
AccelerateDecelerateInterpolator
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
使用了余弦函数。
正常来说,getInterpolation应该满足,输入0时,返回0,输入1时,返回1.
TypeEvaluator
估值器,作用是根据当前属性的百分比来计算改变后的属性值。
ArgbEvaluator (针对Color)
FloatEvaluator
IntEvaluator
IntEvaluator
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
这里的fraction ,和上面getInterpolation的返回值有关【不一定相同】,根据这个值(比例),乘以起始和结束之差,再加上起始值,就得出当前值。
取值的间隔和屏幕刷新率有关,一般是16ms一次
属性动画可以有多个值,如果是线性插值器,每两个值之间的变化时间是相同的。
插值器的变化都是从0--1
估值器的fraction,如果属性有多段变化,就会有多次0---1的变化过程。
animator.getAnimatedFraction() 可以得到当前的fraction
在通过估值器获取值
fraction = mInterpolator.getInterpolation(fraction);?
E/MyLinearInterpolator: getInterpolation: 0.0168
E/MyFloatEvaluator: evaluate: fraction = 0.0168 startValue = 0.0 endValue = 360.0
z_memo_06Else.txt
Q: 如何对一个button的width做动画
1. 补间动画做不到,因为补间动画只能做伸缩平移等操作,无法针对width操作
2. 属性动画无法直接做到
ObjectAnimator.ofInt(tv_demo,"width", tv_demo.dp2px(200f),tv_demo.dp2px(600f)).start()
没有效果。
3. 属性动画要有效果需要两个条件
例如;width属性
1. 对象有setWidth这个方法 (如果没有,程序会Crash)
2. width属性的改变能够通过某种方式显示出来,体现在UI上,否则看上去没效果。
所以:
button虽然有setWidth方法(是用来设置最大和最小宽度的),但是set的width和button的宽度不是同一个东西,因为没有效果可以展示出来。
4. 解决方案
1. 给这个属性加上get、set方法 (不适用,或者需要自定义继承button)
2. 用一个类包装view 【常用】
inner class ViewWrapper(private val view: View) {
fun getWidth(): Int {
return view.layoutParams.width
}
fun setWidth(width: Int) {
view.layoutParams.width = width
view.requestLayout()
}
}
3. 使用ValueAnimator , 结合UpdateListener
z_memo_07Flow.txt
ObjectAnimator.ofInt(ViewWrapper(tv_demo), "width", tv_demo.dp2px(100f), tv_demo.dp2px(300f)).start()
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setIntValues(values);
return anim;
}
// 设置对象和属性集合
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
public void setTarget(@Nullable Object target) {
final Object oldTarget = getTarget();
if (oldTarget != target) {
if (isStarted()) {
cancel();
}
mTarget = target == null ? null : new WeakReference<Object>(target);
// New target should cause re-initialization prior to starting
mInitialized = false;
}
}
public void setPropertyName(@NonNull String propertyName) {
// mValues could be null if this is being constructed piecemeal. Just record the
// propertyName to be used later when setValues() is called if so.
if (mValues != null) {
PropertyValuesHolder valuesHolder = mValues[0];
String oldName = valuesHolder.getPropertyName();
valuesHolder.setPropertyName(propertyName);
mValuesMap.remove(oldName);
mValuesMap.put(propertyName, valuesHolder);
}
mPropertyName = propertyName;
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
@Override
public void setIntValues(int... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofInt(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
}
} else {
super.setIntValues(values);
}
}
mValues 在ValueAnimator中定义,一开始为null
使用propertyName的方式,property一开为null
执行 setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
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;
}
// 准备完成
start()
@Override
public void start() {
AnimationHandler.getInstance().autoCancelBasedOn(this);
//.... 取消当前的动画,调用super
super.start();
}
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);// 关键
}
}
}
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (isPulsingInternal()) {
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
// Only modify the start time when the animation is running. Seek fraction will ensure
// non-running animations skip to the correct start time.
mStartTime = currentTime - seekTime;
} else {
// If the animation loop hasn't started, or during start delay, the startTime will be
// adjusted once the delay has passed based on seek fraction.
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
// 执行动画
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);
}
}
}
// 在ObjectAnimator中重写了
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up. Note: we allow null target if the
/// target has never been set.
cancel();
return;
}
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
}
// mValues[i].setAnimatedValue(target); 反射调用 属性的set方法
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
@Override
void calculateValue(float fraction) {
mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
}
//PropertyValuesHolder
void setupSetterAndGetter(Object target) {
if (mProperty != null) {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (testValue == null) {
testValue = convertBack(mProperty.get(target));
}
kf.setValue(testValue);
kf.setValueWasSetOnStart(true);
}
}
return;
} catch (ClassCastException e) {
Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
") on target object " + target + ". Trying reflection instead");
mProperty = null;
}
}
// We can't just say 'else' here because the catch statement sets mProperty to null.
if (mProperty == null) {
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);
}
List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
try {
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
kf.setValueWasSetOnStart(true);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
}
}
@CallSuper注解主要是用来强调在覆盖父类方法的时候,需要实现父类的方法,及时调用对应的super.**方法,当使用 @CallSuper 修饰了某个方法,
如果子类覆盖父类该方法后没有实现对父类方法的调用就会报错
@CallSuper
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
z_memo_08note.txt
1. 帧动画容易引起oom,尽量避免使用
2. 在activity结束时要及时结束动画,避免内存泄漏
3. view动画有时候会出现setVisibility(View.GONE)无效的情况,需要先clearAnimation,再设置
4. 建议开启硬件加速