# PhotoPicker

> 调用设备摄像头拍摄照片或录制视频，并返回生成文件的路径。

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

# PhotoPicker

`PhotoPicker` 使用设备摄像头**拍照**或录制**视频**（带/不带音频），支持效果（亮度/对比度/锐度）、切换摄像头和可选的确认。它遵循 picker 的模式（参见 [`Picker`](/docs/ui/widgets/picker)），但结果是一个**文件路径** —— 或一个错误字符串。

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

## 何时使用
- 在应用内拍照或录制视频。

## 何时不要使用
- 读取条形码/二维码 → 使用 [`BarcodePicker`](/docs/ui/widgets/barcodepicker)。

## 外观
全屏摄像头预览（黑色背景，无边框），带半透明圆形按钮：取消（X）、拍摄/录制（居中）、切换摄像头和设置/暂停。在带确认的拍照模式下，会有一个确认/重拍屏。

## 完整示例（复制、粘贴并运行）
`PhotoPicker` 是一个摄像头覆盖层 —— 要打开它，请从 `Button` 触发。该示例复用了基本用法，并在 `Label` 中显示返回的路径（或错误）。

```java
package com.exemplo;

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("Nenhuma foto ainda");
      add(LAY_AUTO, saida);

      Button tirarFoto = new Button("Tirar foto")
         .onAction(c ->
         {
            PhotoPicker pp = new PhotoPicker(PhotoPicker.Preview.PHOTO);
            pp.show(answer ->
            {
               String caminho = (String) answer;
               if (pp.isError())
                  saida.setText("Falha na câmera: " + caminho);
               else
                  saida.setText("Foto salva em: " + caminho);
            });
         });
      add(LAY_AUTO, tirarFoto);
   }
}
```

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

## 基本用法
```java
PhotoPicker pp = new PhotoPicker(PhotoPicker.Preview.PHOTO);
pp.show(answer ->
{
   String caminho = (String) answer;
   if (pp.isError())
      new Alert("Falha na câmera", caminho, "OK").show();
   else
      Debug.log("Foto salva em: " + caminho);
});
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `PhotoPicker(Preview type)` | 模式：`Preview.PHOTO`、`Preview.MUTED_VIDEO` 或 `Preview.AUDIO_VIDEO`。 |

## 属性与自定义
| 字段 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `PhotoPicker.defaultJpegQuality`（静态） | `int` | 90 | 保存照片时的 JPG 质量。 |
| `PhotoPicker.showConfirmation`（静态） | `boolean` | `true` | 拍照模式下的确认/重拍屏。 |
| `videoQuality` | `Camera.AVQuality` | HIGH | 视频质量。 |
| `videoTimeLimit` | `int`（秒） | 0 | 录制时长限制（0 = 无限制）。 |

## 回调与事件
| 回调 | 触发时机 |
|---|---|
| `show(onAnswer)` | 关闭时，交付路径 `String`（或 `"ERROR: ..."`）。 |

## 结果（重要）
`getAnswer()` 返回文件的**路径**（照片的 JPG 或视频的 MP4）。若发生摄像头错误，返回字符串 `"ERROR: ..."`。

> ⚠️ **在将该值当作路径处理之前，务必先检查 `isError()`** —— `getAnswer()` 混合了路径和错误消息。

## 状态
- `onAppPause`/`onAppResume` 停止/恢复摄像头；`onRemove` 停止一切并清理监听器。
- 摄像头错误 → 写入 `answer = "ERROR: ..."` 并关闭。

### 带音频且限时 30 秒的视频
```java
PhotoPicker vp = new PhotoPicker(PhotoPicker.Preview.AUDIO_VIDEO);
vp.videoTimeLimit = 30;
vp.show(a -> Debug.log("MP4 em: " + a));
```

## 最佳实践
- 在打开返回的文件之前，**检查 `isError()`**。
- 不要在 `hide()` 之后复用同一个实例 —— 摄像头资源在关闭时被释放。

## 常见错误
- **不检查 `isError()` 就把 `answer` 当作路径处理** —— 它可能是 `"ERROR: ..."`。
- **假设路径可移植** —— 视频路径固定为 `/sdcard/...`（Android）。

## 另请参阅
- 领域指南：[`ui/`](/docs/ui) · 实用配方：[`cookbook/`](/docs/cookbook)
- 模式基础：[`Picker`](/docs/ui/widgets/picker)
- 相关组件：[`BarcodePicker`](/docs/ui/widgets/barcodepicker)
