# Switch

> 开关切换器，带动画滑轨和可选的文字标签，用于表达开启或关闭两种状态。

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

# Switch

`Switch` 是一个**开/关**切换器，带有圆角轨道和一个"thumb"（滑动按钮），thumb 以动画方式在 off 和 on 状态之间滑动。它接受一个可选的旁置文本标签，以及在过渡时进行淡入淡出交叉的 on/off 图标。它是以 iOS/Material 风格立即启用/禁用某个偏好设置的理想控件。

> 🟢 · **包：** `dev.crossfly.ui` · **继承：** `Component`（实现 `Checkable`、`AlphaAnimation.Animate`） · **样式：** `SwitchStyle`

## 何时使用
- 具有即时效果地开/关单个偏好设置（例如"启用通知"）。
- 用户期待一个可视化 toggle 而非勾选框的场合。

## 何时不使用
- 从同时可见的多个选项中选择 → 在 [`group`](/docs/ui/widgets/group) 内使用 [`option`](/docs/ui/widgets/option)。
- 更大的选项列表 → 使用 [`dropdown`](/docs/ui/widgets/dropdown)。

## 外观
圆角轨道（`trackRadius = trackH/2`）加上一个方形 thumb，thumb 从 `thumbMinX`（off）移动到 `thumbMaxX`（on）。宽度、轨道高度和 thumb 大小与字体高度成比例（`SwitchStyle` 的 `switchWidth`、`trackHeight`、`thumbSizeRatio`）。可选的标签位于切换器旁边；当 `iconAlignH == END` 时 switch 在右、文本在左，否则文本在右。

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

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

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

      // 反映 switch 状态的 Label
      Label saida = new Label("Recurso: desligado");
      add(LAY_AUTO, saida);

      // 带标签的 Switch；onAction 只在状态改变时触发
      Switch toggle = new Switch("Ativar recurso");
      toggle.checked(false);   // 初始状态，无动画
      toggle.onAction(c -> saida.setText("Recurso: " + (toggle.checked ? "ligado" : "desligado")));
      add(LAY_AUTO, toggle);
   }
}
```

> 下面的片段**只展示核心部分**（放入 `onShow()` 内的内容）。要运行它们，请将它们放入上面的 `MainWindow` 骨架中。

## 基本用法
```java
Switch toggle = new Switch("Ativar recurso");
toggle.checked(true);                                  // 初始状态，无动画
toggle.onAction(c -> System.out.println("estado: " + toggle.checked));
container.add(LAY_AUTO, toggle);
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `Switch()` | 未选中，无标签（仅图标模式）。 |
| `Switch(String text)` | 未选中，带旁置标签。 |

## 属性与自定义
| 属性 / 方法 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `text(String)` | `Switch`（流式） | `null` | 设置标签。 |
| `checked(boolean)` | `Switch`（流式） | `false` | **无**动画的初始状态（直接调整 `progress`）；通知分组。 |
| `setChecked(boolean)` | `void` | — | **带**动画的状态；通知分组。 |
| `toggle()` | `void` | — | 切换状态（尊重分组）。 |
| `checked` | `boolean`（字段） | `false` | 当前逻辑状态（只读）。 |
| `progress` | `float`（字段） | `0` | thumb 的插值位置：`0` = off，`1` = on。 |
| `.group(Group)` | `Switch`（流式） | — | 关联到一个 [`group`](/docs/ui/widgets/group) 以实现互斥。 |

## 回调与事件
| 回调 | 何时触发 |
|---|---|
| `onAction(Action)` | 在 `onTouchUp` 时，**仅当**状态（`checked`）发生变化时。 |

## 状态
- **checked / unchecked**：由 `checked(boolean)`（无动画）或 `setChecked(boolean)`（带动画）控制。`checked` 字段反映状态。
- **动画中**：`progress` 通过 `AlphaAnimation` 从 0 过渡到 1（或反之）（持续时间 = `style.animationDuration`）。
- **在分组中**：`toggle()` **不会取消选中**一个已选中且属于某分组的 switch（单选行为）。

### 一个 Group 中多个互斥的 switch（单选）
```java
Group modo = new Group();

Switch claro  = new Switch("Tema claro").group(modo).checked(true);
Switch escuro = new Switch("Tema escuro").group(modo);

container.add(LAY_AUTO, claro);
container.add(LAY_AUTO, escuro);
// 勾选一个会自动取消勾选另一个。
```

## 最佳实践
- 对于初始状态优先使用 `checked(boolean)`（一次性调整 `progress` 和分组）；当你想要运行时的动画过渡时使用 `setChecked`。
- 在 `onAction` 内通过 `isChecked()` 或 `checked` 字段读取状态。

## 常见错误
- 期待 `onAction` 在每次交互时都触发：它只在 `checked` **变化**时触发。
- 试图通过点击一个 `Group` 内已选中的 switch 来"关闭"它：`toggle()` 会阻止这一点（单选）。

## 另见
- 领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 概念（按状态的样式）：[`concepts/`](/docs/concepts)
- 相关组件：[`option`](/docs/ui/widgets/option) · [`group`](/docs/ui/widgets/group)
