# Slider

> 在给定区间内选择一个 float 值，可以连续滑动，也可以按固定步进取值。

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

# Slider

`Slider` 允许通过拖动一个滑块（knob）在区间 `[min, max]` 内选择一个 `float` 值。它可以是**连续的**或**离散的**（带步进，通过 `npos`）。方向会自动检测：如果 `宽度 ≥ 高度` 则为**水平**，否则为**垂直**。触摸滑块之外的位置会平滑地动画到所触摸的点；拖动则持续更新值。

> 🟢 · **包：** `dev.crossfly.ui` · **继承：** `Component` · **样式：** `SliderStyle`（带按下变体 `sliderPressed()`）

## 何时使用
- 连续或近似的值：音量、亮度、区间、缩放。
- 用 `npos` 在少量离散步进中选择（例如 1 到 5）。

## 何时不使用
- 当用户需要输入一个精确的数字时 → 使用数字模式的 [`textfield`](/docs/ui/widgets/textfield)。

## 外观
一条分为两段的条 —— **已填充**（滑块之前）和**未填充**（之后）—— 加上滑块。每一段的坐标（`bx1/by1/bw1/bh1`、`bx2/by2/bw2/bh2`）和滑块的坐标（`kx/ky/ks`）在布局时计算（全部为 `float`）。条的粗细为 `thickness * fontHeight`；滑块为 `knobSize * fontHeight`（来自 `SliderStyle`）。

## 完整示例（复制、粘贴并运行）
```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());

      Label saida = new Label("Volume: 50");
      add(LAY_AUTO, saida);

      // 连续 Slider 0..100，从 50 开始；onAction 在每次 value 变化时触发
      Slider volume = new Slider(0, 100, 50);
      volume.onAction(c -> saida.setText("Volume: " + (int) volume.value));
      add(LAY_AUTO, volume);
   }
}
```

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

## 基本用法
```java
Slider volume = new Slider(0, 100, 50);                 // 连续，从 50 开始
volume.onAction(c -> System.out.println("valor: " + volume.value));
container.add(LAY_AUTO, volume);
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `Slider(float min, float max, float value)` | **连续型** slider。 |
| `Slider(float min, float max, float value, int npos)` | **离散型** slider：`npos` 个位置（例如 `5` → 步进在 0%、25%、50%、75%、100%）。 |

## 属性与自定义
| 属性 / 方法 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `value` | `float`（字段） | 构造函数 | 当前值（只读）。 |
| `min` / `max` | `float`（字段） | 构造函数 | 区间的边界。 |
| `npos` | `int`（字段） | `0` | 离散位置数；`0` = 连续。 |
| `setValue(float)` | `void` | — | 立即设置值（= `setValue(v, false)`）；应用 `clamp` + `snap`。 |
| `setValue(float, boolean animated)` | `void` | — | 带/不带动画地设置值；触发 `runActions()`。 |

## 回调与事件
| 回调 | 何时触发 |
|---|---|
| `onAction(Action)` | 每次 `value` 变化时（拖动、点击动画和 `setValue`）。 |

## 状态
- **dragging**：当用户拖动滑块时（`onPan`），`value` 随每次移动而更新。
- **tap-to-jump**：触摸滑块之外的位置会动画到所触摸的点（`PositionAnimation`，持续时间 `style.animationDuration`）。
- **pressed**：使用其自有的按下样式（`sliderPressed()`）。

### 带 5 个档位的离散型 slider
```java
Slider nivel = new Slider(0, 100, 50, 5);               // 步长：0,25,50,75,100
nivel.onAction(c -> System.out.println("nível: " + nivel.value));
container.add(LAY_AUTO, nivel);

nivel.setValue(75, true);   // 动画到 75（会 snap 到最近的步长）
```

## 最佳实践
- 传入一个位于 `[min, max]` 内的初始 `value`：**构造函数不做 clamp**（只有 `setValue` 做）。
- 要强制垂直方向，在布局中给 slider 的高度大于宽度。

## 常见错误
- 假设方向固定：它是在布局时由宽高比决定的。
- 在 `npos > 0` 时期待连续值：在离散模式下，值总是被"吸附"到步进上。

## 另见
- 领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 概念（布局与响应式）：[`concepts/`](/docs/concepts)
- 相关组件：[`progress`](/docs/ui/widgets/progress)
