# 如何读取二维码或条形码

> 使用 BarcodePicker 通过摄像头扫描二维码/条形码，或用 CameraBarcode 解码一张 Image。

Source: https://crossfly.dev/zh/docs/cookbook/device/ler-codigo-de-barras — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 如何读取二维码或条形码

**目的。** 打开摄像头，用 `BarcodePicker` 实时读取 **二维码** 或 **条形码**，
或用 `CameraBarcode` 从一张已捕获的 `Image` 中解码一个码。

## 使用的 API
- `BarcodePicker(String title, BarcodeFormat... formats)` — 用于实时读取的 **抽象** 窗口；你需要 **继承** 它并实现 `onBarcode(BarcodeFormat, String)`。`formats` 为空 = 接受所有格式。
- `abstract void onBarcode(BarcodeFormat format, String code)` — 每当解码出一个通过格式过滤的新码时被调用。
- `BarcodePicker.formats1D` / `formats2D`（静态数组）— 现成的格式集合；`formats2D` 包含 `QR_CODE`。
- `BarcodePicker.beep` / `BarcodePicker.vibrate`（静态 `boolean`）— 解码时的反馈（默认 `true`）。
- `String getAnswer()` — 最后解码出的码，若取消则为 `null`。
- `CameraBarcode.readBarcode(Image img)` → `com.google.zxing.Result`（或 `null`）— 从一张静态图片中解码。**它不是线程安全的。**

> **重要差异。** `BarcodePicker` **是 `abstract` 的**，并且使用 `onBarcode(...)` 方法，而不是
> `RunnableWith` 类型的回调。请实例化一个子类（匿名或具名）。picker 在解码时已经会发出
> `beep()`/`vibrate(20)`，并且 **抑制重复码**（只有在文本变化时才调用 `onBarcode`）。它在读取后
> **不会自动关闭** —— 如果你想在第一次读取时结束，请在 `onBarcode` 内调用 `hide()`。
> 点击 "X"（右下角）会取消。

## 完整示例（复制、粘贴并运行）

```java
package com.exemplo;

import com.google.zxing.*;                  // BarcodeFormat
import dev.crossfly.ui.*;                   // Window, Component, Button, Label
import dev.crossfly.ui.layout.*;            // VerticalLayout
import dev.crossfly.ui.picker.*;            // BarcodePicker

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

      Label resultado = new Label("");
      add(LAY_AUTO, resultado);

      Button acao = new Button("Ler QR Code");
      acao.onAction(c ->
      {
         // 实时读取 — 仅 QR Code；首次读取即关闭
         BarcodePicker picker = new BarcodePicker("Aponte para o QR", BarcodePicker.formats2D)
         {
            protected void onBarcode(BarcodeFormat format, String code)
            {
               // 在 paint thread 之外调用 → 用 onUI(...) 回到 UI
               Component.onUI(() ->
               {
                  resultado.setText("Lido: " + code);
                  hide();            // 首次读取即关闭扫描器（可选）
               });
            }
         };
         picker.show();
      });
      add(LAY_AUTO, acao);
   }
}
```

> 下面的片段只展示 **核心部分**。要运行它，请把它放进上面的 `MainWindow` 骨架里。

## 最小片段

```java
// 已在源码验证：BarcodePicker.java (constructor, onBarcode, formats2D, getAnswer); CameraBarcode.java (readBarcode)
import com.google.zxing.*;                 // BarcodeFormat, Result
import dev.crossfly.ui.picker.*;           // BarcodePicker
import dev.crossfly.ui.*;                  // Component (onUI)

// 1) 实时读取 — 仅 QR Code；首次读取即关闭
BarcodePicker picker = new BarcodePicker("Aponte para o QR", BarcodePicker.formats2D) {
   protected void onBarcode(BarcodeFormat format, String code) {
      // 在 paint thread 之外调用 → 用 onUI(...) 回到 UI
      Component.onUI(() -> {
         processar(code);   // 例如：填写字段、导航等
         hide();            // 首次读取即关闭扫描器（可选）
      });
   }
};
picker.show();             // 构造函数中 formats 留空以接受所有格式

// 2) 从静态 Image 解码（一次一个 camera/thread — 非线程安全）
Result r = CameraBarcode.readBarcode(minhaImagem);
if (r != null) {
   String texto = r.getText();
   BarcodeFormat fmt = r.getBarcodeFormat();
}
```

## 深入了解
- 摄像头指南：[设备摄像头](/docs/data-and-services/device-camera) · 组件：[BarcodePicker](/docs/ui/widgets/barcodepicker)
- 姊妹篇：[tirar-foto.md](/docs/cookbook/device/tirar-foto)
