# 如何拍照并获取文件路径

> 调用设备摄像头拍摄照片，并拿到已保存 JPEG 文件的路径，四个平台写法一致。

Source: https://crossfly.dev/zh/docs/cookbook/device/tirar-foto — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 如何拍照并获取文件路径

**目的。** 打开摄像头、拍摄一张照片并接收已保存的 **JPEG 文件路径**。
推荐的方式是使用 `Camera` 并设置 `cameraType = CameraType.PICKER`，它在底层会打开
`PhotoPicker`（框架自带的拍摄 UI）并返回路径——如果取消则返回 `null`。

## 使用的 API
- `Camera` — 无参构造函数；公共字段 `cameraType`（`CameraType`）、`captureMode`（`Preview`，默认 `PHOTO`）、`videoQuality`（`AVQuality`）、`videoTimeLimit`。
- `Camera.shot()` → `String` — 根据字段拍摄照片/视频并返回**文件路径**或 `null`（取消、出错，或 macOS ARM）。**不存在** `click()` 方法。
- `CameraType`（枚举）— `DEFAULT`（系统原生摄像头）、`PICKER`（通过 `PhotoPicker` 的 CrossFly UI）、`GALLERY`（从图库中选择）。
- `PhotoPicker(Preview type)` — 当 `cameraType == PICKER` 时由 `shot()` 内部使用；暴露 `getAnswer()`（路径或错误字符串）和 `isError()`。`Camera.shot()` 已经为你把错误转换为 `null`。

> **偏差/诚实说明。** `PhotoPicker` **不是**抽象类，也**不**使用回调
> `onAnswer`：它把结果写入 `getAnswer()` 并通过 `isError()` 标示失败。`Camera.shot()`
> 封装了该流程（`show()` → 读取 `getAnswer()` → `isError() ? null : ret`），因此优先使用 `shot()`。
> 在 iOS 上，当 `captureMode != PHOTO` 时，`shot()` 会直接委托给系统原生摄像头。

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

```java
package com.exemplo;

import dev.crossfly.ui.*;                   // Window, Component, Button, Label, UIConstants
import dev.crossfly.ui.layout.*;            // VerticalLayout
import dev.crossfly.device.*;               // Camera

/** 可执行的主界面。将 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("Tirar foto");
      acao.onAction(c ->
      {
         // picker 打开期间 shot() 会阻塞；请在 paint thread 之外运行。
         new Thread(() ->
         {
            Camera cam = new Camera();
            cam.cameraType  = UIConstants.CameraType.PICKER;   // CrossFly 的拍摄 UI
            cam.captureMode = UIConstants.Preview.PHOTO;       // 照片（默认）

            String path = cam.shot();                          // JPEG 的路径，取消/出错则为 null

            // 返回值在 paint thread 之外到达 → 用 onUI(...) 回到 UI
            Component.onUI(() ->
               resultado.setText(path != null ? "Foto: " + path : "Cancelado ou erro"));
         }).start();
      });
      add(LAY_AUTO, acao);
   }
}
```

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

## 最小片段

```java
// 已在源码验证：Camera.java shot()（第 75-91 行）；PhotoPicker.java (constructor + getAnswer/isError)
import dev.crossfly.device.*;              // Camera
import dev.crossfly.ui.*;                  // Component (onUI), UIConstants.CameraType/Preview

// picker 打开期间 shot() 会阻塞；请在 paint thread 之外运行。
new Thread(() -> {
   Camera cam = new Camera();
   cam.cameraType  = UIConstants.CameraType.PICKER;   // CrossFly 的拍摄 UI
   cam.captureMode = UIConstants.Preview.PHOTO;       // 照片（默认）

   String path = cam.shot();                          // JPEG 的路径，取消/出错则为 null

   // 返回值在 paint thread 之外到达 → 用 onUI(...) 回到 UI
   Component.onUI(() -> {
      if (path != null)
         mostrarFoto(path);   // 例如：加载 Image 并在 ImageView 中显示
      // else：用户取消或发生相机错误
   });
}).start();
```

## 深入了解
- 摄像头指南：[设备摄像头](/docs/data-and-services/device-camera) · 控件：[PhotoPicker](/docs/ui/widgets/photopicker)
- 姊妹配方：[gravar-video.md](/docs/cookbook/device/gravar-video) · [ler-codigo-de-barras.md](/docs/cookbook/device/ler-codigo-de-barras)
