0.7 动画

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)

  • 插值器:根据时间流逝百分比计算属性变化百分比(如 LinearInterpolatorDecelerateInterpolator
  • 估值器:根据插值器返回的百分比计算实际属性值(如 FloatEvaluatorArgbEvaluator

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. 建议开启硬件加速
posted @ 2026-01-20 23:02  y丶innocence  阅读(0)  评论(0)    收藏  举报