【阅读源码--Android】动画之辅助类

13.1 TypeEvaluator<T>

职责:在两个动画值之间进行线性插值,将 fraction 映射为实际动画值。

接口定义

java 复制代码
public interface TypeEvaluator<T> {
    T evaluate(float fraction, T startValue, T endValue);
}

核心方法

方法 说明
evaluate(float fraction, T startValue, T endValue) 根据 fraction 在 startValue 和 endValue 之间线性插值,返回计算结果

内置实现

实现类 值类型 插值方式
IntEvaluator Integer startInt + fraction * (endInt - startInt)
FloatEvaluator Number startFloat + fraction * (endFloat - startFloat)
ArgbEvaluator Object (int color) 分离 ARGB 四通道,在 sRGB 线性空间中分别插值后合并(单例模式)
PointFEvaluator PointF 对 x/y 分别线性插值
RectEvaluator Rect 对 left/top/right/bottom 分别插值
FloatArrayEvaluator float[] 对数组每个元素分别插值
IntArrayEvaluator int[] 对数组每个元素分别插值

ArgbEvaluator 详解

ArgbEvaluator 对颜色通道使用 gamma 校正(Math.pow(x, 2.2)),在 sRGB 线性空间中进行插值,而非直接对 sRGB 值线性插值。

复制代码
sRGB [0..255]
    │  / 255.0f                           ← 归一化到 [0,1]
    ▼
sRGB [0,1]
    │  Math.pow(x, 2.2)                   ← sRGB → 线性光(gamma 解码)
    ▼
线性光 [0,1]
    │  start + fraction * (end - start)   ← 线性插值
    ▼
插值后线性光 [0,1]
    │  Math.pow(x, 1.0/2.2) * 255         ← 线性光 → sRGB(gamma 编码)
    ▼
sRGB [0..255]
  • RGB 通道:做 pow 转换,因为人眼对颜色的感知是非线性的,直接在 sRGB 空间插值会导致中间色偏暗
  • Alpha 通道:不做 pow,直接线性插值,因为 Alpha 表示透明度而非光强度
  • 单例模式 :无状态,getInstance() 返回共享实例,可被多个 Animator 复用

TypeConverter<T, V>

职责:在动画值类型 T 与 setter 参数类型 V 之间转换。当动画计算的值类型与目标属性 setter 接受的参数类型不一致时使用。

java 复制代码
public abstract class TypeConverter<T, V> {
    public abstract V convert(T value);    // 动画值 → setter 参数
    Class<V> getTargetType();              // setter 参数类型(用于查找 setter)
    Class<T> getSourceType();               // 动画值类型
}

使用场景 :例如动画沿 Path 运动(产生 PointF),但目标属性 setter 接受 float,需要 TypeConverter<PointF, Float> 提取 x 或 y 分量。

BidirectionalTypeConverter<T, V>

职责 :双向类型转换器,继承自 TypeConverter,额外支持反向转换(setter 参数类型 → 动画值类型)。

java 复制代码
public abstract class BidirectionalTypeConverter<T, V> extends TypeConverter<T, V> {
    public abstract T convertBack(V value);    // setter 参数 → 动画值(从目标对象读取当前值时使用)
    public BidirectionalTypeConverter<V, T> invert();  // 返回反转的 Converter
}

使用场景 :当只提供了动画终值(未提供起始值)时,ObjectAnimator 需要从目标对象 getter 读取当前值,再通过 convertBack 转换为动画值类型。invert() 返回一个反转的 Converter,将 convertconvertBack 互换。

使用方式

  • ValueAnimator.ofObject(evaluator, values) 工厂方法传入
  • ValueAnimator.setEvaluator(evaluator) 后设置
  • PropertyValuesHolder.init() 中自动分配:IntegerIntEvaluatorFloatFloatEvaluator,其他类型需手动设置

13.2 TimeInterpolator

职责:控制动画的"节奏",将原始时间 fraction 映射为插值 fraction,实现非线性运动(加速、减速、弹跳等)。

接口定义

java 复制代码
public interface TimeInterpolator {
    float getInterpolation(float input);
}

核心方法

方法 说明
getInterpolation(float input) 输入 [0,1] 的原始 fraction,返回插值后的 fraction。返回值可超出 [0,1](如过冲、弹跳)

与 TypeEvaluator 的关系

复制代码
原始 fraction (0~1)
    │
    ▼  TimeInterpolator.getInterpolation()
插值 fraction (可能超出 0~1)
    │
    ▼  TypeEvaluator.evaluate()
实际动画值

内置实现 (位于 android.view.animation 包):

实现类 效果
LinearInterpolator 匀速
AccelerateInterpolator 加速
DecelerateInterpolator 减速
AccelerateDecelerateInterpolator 先加速后减速(默认)
BounceInterpolator 弹跳
OvershootInterpolator 过冲后回弹
AnticipateInterpolator 先回拉再前进
AnticipateOvershootInterpolator 先回拉,过冲后回弹
CycleInterpolator 正弦周期循环
PathInterpolator 基于 Path 自定义曲线

在 ValueAnimator 中的调用位置animateValue() 方法中(第1644行):

java 复制代码
fraction = mInterpolator.getInterpolation(fraction);

在 Keyframe 中的局部 Interpolator :每个 Keyframe 可携带独立的 TimeInterpolator,仅作用于该 Keyframe 到下一个 Keyframe 之间的区间。若为 null 则使用线性插值。

13.3 PropertyValuesHolder

职责 :持有一个属性的名称、值集合、Evaluator 和 KeyframeSet,是 ValueAnimator 与具体属性值之间的桥梁。支持多属性并行动画。

核心字段

字段 类型 说明
mPropertyName String 属性名(用于反射查找 setter/getter)
mProperty Property 属性对象(优先于反射方式)
mSetter Method setter 方法(反射缓存)
mGetter Method getter 方法(用于自动获取缺失值)
mValueType Class 值类型
mKeyframes Keyframes 关键帧集合
mEvaluator TypeEvaluator 类型求值器
mConverter TypeConverter 值类型转换器(动画值 → setter 参数类型)
mAnimatedValue Object 最近计算的动画值

工厂方法

方法 说明
ofInt(String/Property, int...) 创建 int 类型动画值持有器
ofFloat(String/Property, float...) 创建 float 类型动画值持有器
ofObject(String/Property, TypeEvaluator, Object...) 创建自定义对象类型动画值持有器
ofObject(String/Property, TypeConverter, Path) 沿 Path 动画,通过 Converter 转换为属性值
ofKeyframe(String/Property, Keyframe...) 通过 Keyframe 数组创建
ofMultiInt(String, int[][]/Path/...) 多参数 int setter
ofMultiFloat(String, float[][]/Path/...) 多参数 float setter

核心方法

方法 说明
calculateValue(float fraction) 调用 mKeyframes.getValue(fraction) 计算当前值,再经 mConverter 转换
setAnimatedValue(Object target) 将计算出的值通过 mProperty.set()mSetter.invoke() 设置到目标对象
setupSetterAndGetter(Object target) 动画启动前,通过反射或 Property 自动查找 setter/getter,并为缺失值的 Keyframe 填充值
init() 初始化 Evaluator(自动分配 Int/Float Evaluator)并传递给 KeyframeSet
setEvaluator(TypeEvaluator) 设置自定义 Evaluator
setConverter(TypeConverter) 设置类型转换器

内部子类

子类 说明
IntPropertyValuesHolder int 专用,使用 IntKeyframes 避免装箱,支持 JNI setter 加速
FloatPropertyValuesHolder float 专用,使用 FloatKeyframes 避免装箱,支持 JNI setter 加速
MultiFloatValuesHolder 多参数 float setter(如 setLocation(float x, float y)
MultiIntValuesHolder 多参数 int setter

Setter 查找策略(优先级从高到低):

  1. IntProperty / FloatProperty(直接调用,无反射)
  2. Property.set()(通用 Property 接口)
  3. JNI 方法 ID(nGetIntMethod / nGetFloatMethod,仅 int/float)
  4. 反射 Method.invoke()(回退方案)

Setter 类型匹配:当值类型与 setter 参数类型不完全匹配时,按类型变体表依次尝试:

  • Float 变体:float → Float → double → int → Double → Integer
  • Integer 变体:int → Integer → float → double → Float → Double
Setter/Getter 查找详解

查找时机ObjectAnimator 启动时调用 setupSetterAndGetter(target),在动画开始前完成 setter/getter 的绑定。

查找流程

复制代码
setupSetterAndGetter(target):
│
├─ 若 mProperty != null(优先使用 Property 接口)
│   │
│   ├─ 尝试 mProperty.get(target) 验证属性存在
│   │   ├─ 成功 → 遍历 Keyframe,为无值帧填充从 target 读取的值
│   │   └─ ClassCastException → mProperty = null,降级到反射 ↓
│
└─ 若 mProperty == null(反射方式)
    │
    ├─ setupSetter(targetClass)
    │   │
    │   ├─ 确定 setter 参数类型:
    │   │   mConverter != null → mConverter.getTargetType()
    │   │   mConverter == null → mValueType
    │   │
    │   ├─ getPropertyFunction(targetClass, "set", paramType)
    │   │   │
    │   │   ├─ 方法名 = "set" + 首字母大写(mPropertyName)
    │   │   │   例: mPropertyName="alpha" → "setAlpha"
    │   │   │
    │   │   ├─ 按类型变体表依次尝试:
    │   │   │   paramType=Float → 尝试 [float, Float, double, int, Double, Integer]
    │   │   │   paramType=Integer → 尝试 [int, Integer, float, double, Float, Double]
    │   │   │   其他 → 仅尝试 paramType 本身
    │   │   │
    │   │   └─ 找到后缓存到 mSetter,并更新 mValueType 为实际匹配类型
    │   │
    │   └─ 缓存到 sSetterPropertyMap(Class → HashMap<propName, Method>)
    │
    └─ 遍历 Keyframe,为无值帧填充:
        │
        ├─ 若该帧 !hasValue() 或 valueWasSetOnStart()
        │   │
        ├─ setupGetter(targetClass)  ← 按需延迟加载
        │   │   └─ getPropertyFunction(targetClass, "get", null)
        │   │       方法名 = "get" + 首字母大写(mPropertyName)
        │   │       例: mPropertyName="alpha" → "getAlpha"
        │   │
        └─ mGetter.invoke(target) 获取当前值 → 填入 Keyframe

Setter 调用链setAnimatedValue(target) 时):

复制代码
setAnimatedValue(target):
│
├─ IntPropertyValuesHolder / FloatPropertyValuesHolder:
│   ├─ mIntProperty / mFloatProperty != null → 直接调用 setValue(target, value)
│   ├─ mProperty != null → mProperty.set(target, value)
│   ├─ mJniSetter != 0 → nCallIntMethod / nCallFloatMethod(JNI 加速)
│   └─ mSetter != null → mSetter.invoke(target, value)(反射回退)
│
└─ 通用 PropertyValuesHolder:
    ├─ mProperty != null → mProperty.set(target, getAnimatedValue())
    └─ mSetter != null → mSetter.invoke(target, getAnimatedValue())

Getter 用途 :仅用于 ObjectAnimator 场景,当 Keyframe 未提供值时(如 ofFloat("alpha", 1f) 只给了终值),从目标对象 getter 读取当前值作为起始值。ValueAnimator 不使用 getter。

反射缓存sSetterPropertyMapsGetterPropertyMapHashMap<Class, HashMap<String, Method>> 静态缓存,避免对同一个 Class + propertyName 组合重复反射查找。IntPropertyValuesHolderFloatPropertyValuesHolder 额外维护 sJNISetterPropertyMap 缓存 JNI 方法 ID。

13.4 Keyframe

职责 :持有一个时间/值对,定义动画在某个时间点的目标值。每个 Keyframe 可携带独立的 TimeInterpolator 控制该帧到下一帧之间的插值方式。

核心字段

字段 类型 说明
mFraction float 时间点,[0, 1] 表示动画进度的比例
mValueType Class 值类型
mInterpolator TimeInterpolator 该帧到下一帧之间的插值器(null = 线性)
mHasValue boolean 是否有有效值(无值时由 ObjectAnimator 从目标对象自动获取)
mValueWasSetOnStart boolean 值是否在动画启动时从目标对象读取(目标变化时需重新获取)

工厂方法

方法 说明
ofInt(float fraction, int value) 创建带值的 int Keyframe
ofInt(float fraction) 创建无值的 int Keyframe(值从目标对象获取)
ofFloat(float fraction, float value) 创建带值的 float Keyframe
ofFloat(float fraction) 创建无值的 float Keyframe
ofObject(float fraction, Object value) 创建带值的 Object Keyframe
ofObject(float fraction) 创建无值的 Object Keyframe

核心方法

方法 说明
getValue() 获取该帧的值(抽象方法,子类实现)
setValue(Object) 设置该帧的值
getFraction() 获取时间点
setFraction(float) 设置时间点
getInterpolator() 获取该帧的插值器
setInterpolator(TimeInterpolator) 设置该帧的插值器
getType() 获取值类型
hasValue() 是否有有效值

内部子类

子类 值存储 说明
IntKeyframe int mValue 避免装箱,提供 getIntValue() 直接返回原始类型
FloatKeyframe float mValue 避免装箱,提供 getFloatValue() 直接返回原始类型
ObjectKeyframe Object mValue 通用对象类型

13.5 Keyframes(接口)

职责 :抽象 Keyframe 集合,由 ValueAnimator 调用来计算给定 fraction 对应的动画值。

接口定义

java 复制代码
public interface Keyframes extends Cloneable {
    void setEvaluator(TypeEvaluator evaluator);
    Class getType();
    Object getValue(float fraction);
    List<Keyframe> getKeyframes();
    Keyframes clone();

    // 特化子接口,避免装箱
    interface IntKeyframes extends Keyframes {
        int getIntValue(float fraction);
    }
    interface FloatKeyframes extends Keyframes {
        float getFloatValue(float fraction);
    }
}

核心方法

方法 说明
getValue(float fraction) 根据 fraction 在 Keyframe 之间插值,返回动画值(Object 类型)
setEvaluator(TypeEvaluator) 设置 Evaluator(仅 Object 类型需要,int/float 有内置实现)
getType() 返回值类型
getKeyframes() 返回 Keyframe 列表

特化子接口IntKeyframesFloatKeyframes 提供直接返回原始类型的方法,避免装箱开销。

13.6 KeyframeSet

职责Keyframes 接口的基本实现,持有一组 Keyframe 并负责在相邻 Keyframe 之间插值。

核心字段

字段 类型 说明
mNumKeyframes int Keyframe 数量
mFirstKeyframe Keyframe 首帧引用(快速访问)
mLastKeyframe Keyframe 末帧引用(快速访问)
mInterpolator TimeInterpolator 仅 2 帧时使用的插值器(取自末帧)
mKeyframes List<Keyframe> Keyframe 列表
mEvaluator TypeEvaluator 类型求值器

工厂方法

方法 说明
ofInt(int...) 从 int 值数组创建 IntKeyframeSet,自动均匀分配 fraction
ofFloat(float...) 从 float 值数组创建 FloatKeyframeSet,自动均匀分配 fraction
ofObject(Object...) 从 Object 值数组创建 KeyframeSet
ofKeyframe(Keyframe...) 从 Keyframe 数组创建,自动识别类型并返回对应子类
ofPath(Path) 从 Path 创建 PathKeyframes

getValue(float fraction) 插值逻辑

  1. 2 帧优化 :直接用末帧的 Interpolator 插值后调用 mEvaluator.evaluate()
  2. fraction <= 0:在首帧和第二帧之间插值
  3. fraction >= 1:在倒数第二帧和末帧之间插值
  4. 中间值 :遍历找到 fraction 落入的区间 [prevKeyframe, nextKeyframe],计算区间内比例 intervalFraction,应用该区间 Interpolator,调用 mEvaluator.evaluate()

子类

子类 说明
IntKeyframeSet int 专用,实现 getIntValue() 直接使用原始类型计算,避免装箱
FloatKeyframeSet float 专用,实现 getFloatValue() 直接使用原始类型计算,避免装箱
PathKeyframes 基于 Path 的关键帧,将 fraction 映射为 Path 上的 PointF
PathKeyframes 详解

职责 :将 android.graphics.Path 近似为一组线段,通过线性插值在 Path 上定位点。不使用 Keyframe 对象,而是将所有点数据存储在一个紧凑的 float[] 中以减少对象分配。

数据结构

java 复制代码
// 每个点存储 3 个 float:[fraction, x, y]
private static final int FRACTION_OFFSET = 0;
private static final int X_OFFSET = 1;
private static final int Y_OFFSET = 2;
private static final int NUM_COMPONENTS = 3;

private float[] mKeyframeData;  // 紧凑数组,由 path.approximate(error) 生成
private PointF mTempPointF;      // 复用对象,避免每次创建

path.approximate(0.5f) 递归地将 Path 细分为线段,直到线段与曲线的误差小于 0.5 像素,返回 [fraction0, x0, y0, fraction1, x1, y1, ...] 数组。

getValue(float fraction) 查找方式

KeyframeSet 的线性遍历不同,PathKeyframes 使用二分查找定位区间:

复制代码
getValue(fraction):
│
├─ fraction < 0 → interpolateInRange(fraction, 0, 1)           ← 边界外:首段外推
├─ fraction > 1 → interpolateInRange(fraction, numPoints-2, numPoints-1)  ← 边界外:末段外推
├─ fraction == 0 → pointForIndex(0)                              ← 精确起点
├─ fraction == 1 → pointForIndex(numPoints-1)                   ← 精确终点
│
└─ 0 < fraction < 1 → 二分查找:
    │
    ├─ low=0, high=numPoints-1
    │
    ├─ while (low <= high):
    │   mid = (low + high) / 2
    │   midFraction = mKeyframeData[mid * 3]
    │   │
    │   ├─ fraction < midFraction → high = mid - 1
    │   ├─ fraction > midFraction → low = mid + 1
    │   └─ fraction == midFraction → return pointForIndex(mid)  ← 精确命中
    │
    └─ 循环结束后: high < fraction < low
        → interpolateInRange(fraction, high, low)               ← 区间内插值

区间内插值

java 复制代码
PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
    float startFraction = mKeyframeData[startIndex * 3];
    float endFraction   = mKeyframeData[endIndex * 3];

    // 区间内比例
    float intervalFraction = (fraction - startFraction) / (endFraction - startFraction);

    // 对 x、y 分别线性插值
    float x = startValue + (endValue - startValue) * intervalFraction;
    float y = startValue + (endValue - startValue) * intervalFraction;

    mTempPointF.set(x, y);  // 复用对象
    return mTempPointF;
}

与 KeyframeSet 的对比

特性 KeyframeSet PathKeyframes
数据存储 List<Keyframe> 对象列表 紧凑 float[] 数组
查找方式 线性遍历 二分查找
每个点的 Interpolator 支持(每段独立) 不支持(仅线性)
Evaluator 使用 mEvaluator 不使用(直接计算 x/y)
对象分配 每个 Keyframe 一个对象 仅复用一个 PointF
getKeyframes() 返回实际列表 返回 EMPTY_KEYFRAMES

X/Y 分量提取

PathKeyframes 可通过 createXFloatKeyframes() / createYFloatKeyframes() 创建只返回 x 或 y 分量的 FloatKeyframes,通过 createXIntKeyframes() / createYIntKeyframes() 创建 IntKeyframes(四舍五入)。这些适配器内部委托 PathKeyframes.getValue() 再提取分量。

13.7 类关系总览

复制代码
ValueAnimator
    │
    ├── mInterpolator: TimeInterpolator          ← 全局时间插值
    │
    ├── mValues: PropertyValuesHolder[]          ← 多属性并行
    │       │
    │       ├── mKeyframes: Keyframes             ← 关键帧集合
    │       │       │
    │       │       └── KeyframeSet              ← 默认实现
    │       │               │
    │       │               ├── IntKeyframeSet   ← int 特化
    │       │               ├── FloatKeyframeSet  ← float 特化
    │       │               └── PathKeyframes    ← Path 特化
    │       │
    │       ├── mEvaluator: TypeEvaluator         ← 类型求值器
    │       │
    │       ├── mConverter: TypeConverter          ← 值类型转换
    │       │
    │       └── mSetter / mGetter: Method        ← 反射 setter/getter
    │
    └── Keyframe                                 ← 单个关键帧
            ├── IntKeyframe
            ├── FloatKeyframe
            └── ObjectKeyframe

数据流

复制代码
ValueAnimator.animateValue(fraction)
    │
    ├─ mInterpolator.getInterpolation(fraction)     → 插值 fraction
    │
    └─ mValues[i].calculateValue(interpolatedFraction)
            │
            └─ mKeyframes.getValue(fraction)
                    │
                    ├─ 找到区间 [prevKf, nextKf]
                    ├─ nextKf.getInterpolator().getInterpolation(intervalFraction)
                    └─ mEvaluator.evaluate(intervalFraction, prevValue, nextValue)
                            │
                            ▼
                    mAnimatedValue (最终动画值)
相关推荐
weixin_440784111 小时前
【IntentSeivice实现原理】
android·java·开发语言·intentservice
消失的旧时光-19435 小时前
第 2 篇:Android 控制屏为什么与机器人主控使用 TCP 长连接?
android·tcp/ip·机器人
小Ti客栈6 小时前
MySQL查询原理:从Server到InnoDB
android·mysql·adb
纪念 2297 小时前
MySQL(题目讲解二)
android·数据库·mysql
你听得到117 小时前
排查 App 问题:别只盯着报错,把前后发生的事情串起来
android·前端·flutter
淡淡的香烟8 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android·服务器·音视频
Xxtaoaooo8 小时前
从故障诊断到寿命预测:用 DolphinDB 构建工业设备的预测性维护管线
android·故障诊断·dolphindb·工业设备·寿命预测
天空之城--8 小时前
近一周Android Flutter行业动态与实用参考
android·flutter
Everbrilliant898 小时前
Android FFmpeg 实战:从基础到播放器的完整技术解析
android·ffmpeg·ffmepg实战·音视频同步实现·ffmpegpractices·ffmpegpractice·opengl 视频渲染