# 数值动画

> AlphaAnimation、PositionAnimation 和 LoopAnimation — 对一个数值进行插值并应用它。

Source: https://crossfly.dev/zh/docs/ui/animations/value-animations — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 数值动画

这三个动画器**随时间对一个数值进行插值**。它们**不**绘制任何内容：在 `onUpdate` 中你读取
`getValue()` 并将其应用到需要的地方（alpha、位置、颜色…）。由于渲染是按需的，请在回调中调用
`Component.repaint()`。

## 对比
| 类 | 取值范围 | 曲线 | 是否重复？ | 用途 |
|---|---|---|---|---|
| `AlphaAnimation` | **[0,1]** | 三次 ease-in-out | 否（有 `onComplete`） | 淡入淡出、不透明度、高亮 |
| `PositionAnimation` | **自由** | 线性 | 否（有 `onComplete`） | 按像素移动、滑动 |
| `LoopAnimation` | [0,1] | 线性或缓动 | **是，无限循环** | 脉动、光晕、加载 |

## 完整示例（复制、粘贴并运行）
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.animation.*;   // AlphaAnimation, PositionAnimation, LoopAnimation
import dev.crossfly.util.*;           // Debug

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // AlphaAnimation — 标签淡入（0.3 秒内 0→1）
      Label card = new Label("Olá");
      add(LAY_AUTO, card);
      AlphaAnimation fade = new AlphaAnimation(0f, 1f, 0.3f);
      fade.onUpdate(() -> { card.alpha(fade.getValue()); Component.repaint(); })
          .onComplete(() -> Debug.log("fade concluído"))
          .start();

      // PositionAnimation — 面板从右侧滑入（以像素计，自由）
      Label painel = new Label("Painel");
      add(LAY_AUTO, painel);
      int y0 = painel.y, w = painel.width, h = painel.height;
      PositionAnimation slide = new PositionAnimation(Screen.width, 0, 0.4f);
      slide.onUpdate(() -> { painel.setBounds((int) slide.getValue(), y0, w, h); Component.repaint(); })
           .start();

      // LoopAnimation — 持续的不透明度脉动（1 秒周期，平滑，无限）
      Label ponto = new Label("●");
      add(LAY_AUTO, ponto);
      LoopAnimation pulse = new LoopAnimation(1.0f, 60f, true);
      pulse.onUpdate(() -> { ponto.alpha(0.4f + 0.6f * pulse.getValue()); Component.repaint(); })
           .start();
   }
}
```
> 下面的片段只展示了**核心部分**（放入 `onShow()` 内部的内容）。要运行它们，请将其放入上面的 `MainWindow` 骨架中。

## AlphaAnimation — 淡入
```java
Label card = new Label("Olá");
AlphaAnimation fade = new AlphaAnimation(0f, 1f, 0.3f);   // 0.3 秒内 0→1
fade.onUpdate(() -> { card.alpha(fade.getValue()); Component.repaint(); })
    .onComplete(() -> Debug.log("fade concluído"))
    .start();
```
构造函数：`AlphaAnimation(start, end, duration)`（60 FPS）或 `(start, end, duration, fps)`。

## PositionAnimation — 滑动一个面板（按像素）
```java
int y0 = painel.y, w = painel.width, h = painel.height;
PositionAnimation slide = new PositionAnimation(Screen.width, 0, 0.4f);  // 从右侧进入
slide.onUpdate(() -> { painel.setBounds((int) slide.getValue(), y0, w, h); Component.repaint(); })
     .start();
```
它不限于 [0,1]，且是**线性**的 — 非常适合坐标。

## LoopAnimation — 持续脉动
```java
LoopAnimation pulse = new LoopAnimation(1.0f, 60f, true);   // 1 秒周期，60 FPS，平滑
pulse.onUpdate(() -> { float v = 0.4f + 0.6f * pulse.getValue(); ponto.alpha(v); Component.repaint(); })
     .start();
// pulse.stop();   // 离开屏幕时停止
```
构造函数：`LoopAnimation(duration)`（线性，60 FPS）或 `(duration, fps, easeInOut)`。它**没有**
`onComplete`（会一直运行直到 `stop()`）。

## 通用控制
`start()` · `stop()`（在 Alpha/Position 上触发 `onComplete`）· `cancel()`（不触发）·
`isRunning()` · `getValue()`。

## 最佳实践
- **始终在 `onUpdate` 中调用 `Component.repaint()`** — 渲染是按需的。
- **停止循环**（`stop()`），在离开界面时（在 `onRemove()` 中），否则 `Timer` 会继续运行。
- 将动画保存到变量/字段中，以便之后能够 `stop()`/`cancel()`。

## 常见错误
- 使用 `PositionAnimation` 却期望取值范围为 [0,1] — 它是自由的；进行钳制的是 `AlphaAnimation`。
- 忘记 `repaint()` 而导致"动画不显示"。
- 调用 `setAlpha(...)`（来自旧的 Javadoc）— 真正的方法是 **`alpha(float)`**。

## 另请参阅
- [屏幕转场](/docs/ui/animations/screen-transitions) · [触摸与反馈](/docs/ui/animations/press-and-feedback)
