12. 拍摄、保存、显示

演示如何将 RICOH THETA X 相机与 Android 和 IOS 手机配合使用。

特点

  • 拍照
  • 将图片保存到相册
  • 从相册选择图片并显示
  • 以 360 度视图查看图片

diagram

资源

此应用程序是根据以下教程构建的。

  • THETA 概念 3 (使用 Bloc 管理拍照)
  • THETA 概念 6 (在继续之前检查相机状态)
  • THETA 概念 9 (将图片保存到相册)
  • THETA 概念 11 (从相册选择图片)

screen

关键 Flutter 包

  • panorama – 查看带导航的 360 度图片
  • image_picker – 从相册选择图片
  • gallery_saver – 将图片保存到相册
  • flutter_bloc – 管理状态

360 度查看图片

该项目使用 panorama 包以 360 度视图查看图片。当用户点击图片时,Navigator.push 会将其全屏显示。

class PanoramaScreen extends StatefulWidget {
  File myFile;
  PanoramaScreen({Key? key, required this.myFile}) : super(key: key);

  @override
  State<PanoramaScreen> createState() => _PanoramaScreenState();
}

class _PanoramaScreenState extends State<PanoramaScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Panorama(child: Image.file(widget.myFile)),
        ));
  }
}

拍照

有关该过程的更多描述,请参阅 教程 6. RICOH THETA API 命令序列

该项目执行 RICOH THETA API 中的 camera.takePicture 命令。它会持续检查 takePicture 过程是否通过 commands/status 完成。当图片处理完成后,项目将进入下一步:将图片保存到相册。

 on<PictureEvent>((event, emit) async {
      var url = Uri.parse('http://192.168.1.1/osc/commands/execute');
      var header = {'Content-Type': 'application/json;charset=utf-8'};
      var bodyMap = {'name': 'camera.takePicture'};
      var bodyJson = jsonEncode(bodyMap);
      var response = await http.post(url, headers: header, body: bodyJson);
      var convertResponse = jsonDecode(response.body);
      var id = convertResponse['id'];

      if (convertResponse != null && id != null) {
        emit(ThetaState(message: response.body, id: id));
        while (state.cameraState != "done") {
          add(CameraStatusEvent());
          await Future.delayed(Duration(milliseconds: 200));
          print(state.cameraState);
        }
      }
      add(GetFileEvent());
    });

将图片保存到相册

GetFileEvent 使用 camera.listFiles 从相机检索最后一个文件 URL。它从响应中解析出 URL,并使用文件 URL 更新状态。

on<GetFileEvent>((event, emit) async {
    ...
 var fileUrl = convertResponse['results']['entries'][0]['fileUrl'];
      emit(state.copyWith(fileUrl: fileUrl));
...
} 

gallery_saver 包导入到项目中,并在 AndroidManifest.xml 文件中添加权限。

android.permission.WRITE_EXTERNAL_STORAGE

使用 SaveFileEvent 中的 GallerySaver.saveImage 保存图片,并通知状态图片已保存完成。

 on<SaveFileEvent>((event, emit) async {
      await GallerySaver.saveImage(state.fileUrl).then((value) {
        emit(state.copyWith(finishedSaving: true));
      });
    });

从相册选择图片

该应用程序的这一部分遵循了 Smirty 的 Flutter 学习 教程。

theta_event.dart

...
class ImagePickerEvent extends ThetaEvent {
  final XFile image;

  ImagePickerEvent(this.image);
}

theta_bloc.dart

 on<ImagePickerEvent>((event, emit) async {
      emit(state.copyWith(images: event.image));
    });

main.dart

 IconButton(
    onPressed: () async {
        final image = await ImagePicker().pickImage(
        source: ImageSource.gallery,
        );
        if (image == null) return;
        context
            .read<ThetaBloc>()
            .add(ImagePickerEvent(image));
    },
    icon: Icon(Icons.image)),

当按下 IconButton 时,它会使用 ImagePicker 中的文件添加 ImagePickerEvent。在 Bloc 文件中,ImagePickerEvent 会使用文件更新状态。

Bloc 结构

该项目使用 flutter_bloc 包来处理状态管理。事件与发生的每个操作相关联。状态在参数和主构造函数中保存信息。在 Bloc 文件中,有 on 方法来处理每个事件的调用。

状态构造函数的示例

class ThetaState extends Equatable {
  final String message;
  final String fileUrl;
  final String cameraState;
  final String id;
  final bool finishedSaving;
  final XFile? images;

  ThetaState(
      {required this.message,
      this.fileUrl = "",
      this.cameraState = "initial",
      this.id = "",
      this.finishedSaving = false,
      this.images});}

GitHub

查看 Github