惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
C
Cisco Blogs
WordPress大学
WordPress大学
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Cloudflare Blog
小众软件
小众软件
Recent Commits to openclaw:main
Recent Commits to openclaw:main
I
Intezer
Cyberwarzone
Cyberwarzone
T
The Blog of Author Tim Ferriss
博客园 - Franky
F
Fortinet All Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
Recent Announcements
Recent Announcements
I
InfoQ
T
Threat Research - Cisco Blogs
V
V2EX
T
Tenable Blog
H
Help Net Security
Cisco Talos Blog
Cisco Talos Blog
T
Tailwind CSS Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
The GitHub Blog
The GitHub Blog
P
Privacy & Cybersecurity Law Blog
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
D
DataBreaches.Net
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
D
Docker
T
Tor Project blog
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
H
Heimdal Security Blog
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
Martin Fowler
Martin Fowler
AWS News Blog
AWS News Blog
IT之家
IT之家
Google DeepMind News
Google DeepMind News
NISL@THU
NISL@THU
Google Online Security Blog
Google Online Security Blog
Vercel News
Vercel News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
SecWiki News
SecWiki News
GbyAI
GbyAI
P
Proofpoint News Feed
月光博客
月光博客
Schneier on Security
Schneier on Security

博客园 - y丶innocence

临时111 RecyclerView 数据多时无法滑动:ConstraintLayout 约束高度修复笔记 Android Kotlin OkHttp3 WebSocket 长连接与 Gson 数据解析系统笔记 Android + Kotlin + OkHttp WebSocket 相关概念与使用流程笔记(TLS/证书 + 鉴权/会话) AI对话导出markdown格式流程 代理转发 分享文件 面向全球的app的excel导出和kotlin IO原理 安卓导出笔记(未整理) java&kotlin listener 0.4 View 工作流程 0.3 view 滑动冲突 13. Jetpack 0. 安卓开发艺术探索参考资料 12. Material Design 7. 持久化技术 5. Fragment java 基础 4. UI 开发 3. Activity 2.2 Kotlin 面向对象 2.3 Kotlin高级 2.1 Kotlin基础 1. Android简介 [OpenJudge] 反正切函数的应用 (枚举)(数学) [OpenJudge] 摘花生 (模拟)
0.7 动画
y丶innocence · 2026-01-20 · via 博客园 - y丶innocence

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. 建议开启硬件加速