# BarcodePicker

> 通过设备摄像头读取条形码与二维码的抽象类，由各平台提供具体实现。

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

# BarcodePicker

`BarcodePicker` 从实时摄像头帧中读取**条形码/二维码**，并按格式过滤。它是一个**抽象类**：使用者实现 `onBarcode(BarcodeFormat format, String code)`。它遵循 picker 模式（见 [`Picker`](/docs/ui/widgets/picker)）——通过 `getAnswer()` 返回读取到的负载。

> 🔴 · **包：** `dev.crossfly.ui.picker` · **继承：** `Picker implements GdxCamera.Listener`（抽象） · **硬件：** 摄像头

> ℹ️ 这个 `BarcodePicker` 是**通过摄像头读取**，仍然存在。不要与旧的条形码*生成*工具（`util/barcode`）混淆，后者已在现代化过程中被移除。

## 何时使用
- 扫描条形码（商品、门票）或二维码。

## 何时不要使用
- 拍照/录像 → 使用 [`PhotoPicker`](/docs/ui/widgets/photopicker)。

## 外观
全屏摄像头预览（黑色背景），顶部标题，中央有一个用于对准的**红色十字准星**，右下角有一个白色的“X”关闭按钮。

## 完整示例（复制、粘贴并运行）
`BarcodePicker` 是**抽象的**：用一个实现 `onBarcode` 的类体来实例化它。由于它是一个摄像头叠加层，请从 `Button` 触发它。该示例复用了基本用法（1D 格式，首次读取即关闭），并将负载写入 `Label`。`BarcodeFormat` 来自 `com.google.zxing`。

```java
package com.exemplo;

import com.google.zxing.BarcodeFormat;

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

/** 可执行的主界面。将 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("Nenhum código lido");
      add(LAY_AUTO, saida);

      Button escanear = new Button("Escanear código")
         .onAction(c ->
         {
            BarcodePicker scanner = new BarcodePicker("Aponte para o código", BarcodePicker.formats1D)
            {
               protected void onBarcode(BarcodeFormat format, String code)
               {
                  onUI(() -> saida.setText(format + ": " + code)); // 返回 paint thread
                  hide(); // 首次读取后关闭（不会自动关闭）
               }
            };
            scanner.show(answer -> {});
         });
      add(LAY_AUTO, escanear);
   }
}
```

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

## 基本用法
```java
BarcodePicker scanner = new BarcodePicker("Aponte para o código", BarcodePicker.formats1D)
{
   protected void onBarcode(BarcodeFormat format, String code)
   {
      Debug.log(format + ": " + code);
      hide(); // 首次读取后关闭
   }
};
scanner.show(answer -> campoCodigo.setText((String) answer));
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `BarcodePicker(String title, BarcodeFormat... formats)` | 标题 + 接受的格式（varargs；为空 = 全部）。抽象——用实现 `onBarcode` 的类体来实例化。 |

## 属性与自定义
| 成员 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `BarcodePicker.formats1D`（静态） | `BarcodeFormat[]` | — | 现成的 1D 格式集合（CODE_128、EAN_13……）。 |
| `BarcodePicker.formats2D`（静态） | `BarcodeFormat[]` | — | 现成的 2D 格式集合（QR_CODE）。 |
| `BarcodePicker.beep`（静态） | `boolean` | `true` | 解码时发出蜂鸣声。 |
| `BarcodePicker.vibrate`（静态） | `boolean` | `true` | 解码时震动。 |

## 回调与事件
| 回调 | 触发时机 |
|---|---|
| `onBarcode(BarcodeFormat, String)` | （抽象）解码到一个受支持格式的码时。 |
| `show(onAnswer)` | 关闭时，传递最后读取到的负载 `String`。 |

`getAnswer()` 返回最后解码到的负载，若取消则返回 `null`。

## 状态
- **检测到时没有自动 `hide()`** —— 是否关闭由你的 `onBarcode`（调用 `hide()`）决定。不调用时，扫描器会继续读取（适用于连续读取）。
- 连续重复的码（相同的 `lastCode`）会被忽略。
- `onAppPause`/`onAppResume` 管理摄像头；`onRemove` 停止解码线程。

### 仅接受二维码并在首次读取时关闭
```java
BarcodePicker scanner = new BarcodePicker("Leia o QR", BarcodePicker.formats2D)
{
   protected void onBarcode(BarcodeFormat format, String code)
   {
      onUI(() -> campoCodigo.setText(code)); // 在 paint 线程上更新 UI
      hide();
   }
};
scanner.show(answer -> Debug.log("Lido: " + answer));
```

## 最佳实践
- 在 `onBarcode` 中调用 `hide()` 实现单次读取；省略则实现连续读取。
- 解码在后台线程运行——将 UI 更新包裹在 `onUI(...)` 中。

## 常见错误
- **忘记 `hide()`** 却想要单次读取 —— 扫描器会持续激活。
- **直接在 `onBarcode` 中更新 UI** —— 请用 `onUI(...)` 遵守绘制线程。

## 另见
- 该领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 模式基类：[`Picker`](/docs/ui/widgets/picker)
- 相关组件：[`PhotoPicker`](/docs/ui/widgets/photopicker)
