mirror of
https://github.com/sony/flutter-elinux-plugins.git
synced 2025-08-26 03:00:16 +08:00
[camera][draft] Add camera plugin (#29)
Added the first draft version, but there are still a lot of unimplemented features.
This commit is contained in:

committed by
GitHub

parent
4c37b98e79
commit
999fdad59c
971
packages/camera/example/lib/main.dart
Normal file
971
packages/camera/example/lib/main.dart
Normal file
@ -0,0 +1,971 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class CameraExampleHome extends StatefulWidget {
|
||||
@override
|
||||
_CameraExampleHomeState createState() {
|
||||
return _CameraExampleHomeState();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a suitable camera icon for [direction].
|
||||
IconData getCameraLensIcon(CameraLensDirection direction) {
|
||||
switch (direction) {
|
||||
case CameraLensDirection.back:
|
||||
return Icons.camera_rear;
|
||||
case CameraLensDirection.front:
|
||||
return Icons.camera_front;
|
||||
case CameraLensDirection.external:
|
||||
return Icons.camera;
|
||||
default:
|
||||
throw ArgumentError('Unknown lens direction');
|
||||
}
|
||||
}
|
||||
|
||||
void logError(String code, String? message) {
|
||||
if (message != null) {
|
||||
print('Error: $code\nError Message: $message');
|
||||
} else {
|
||||
print('Error: $code');
|
||||
}
|
||||
}
|
||||
|
||||
class _CameraExampleHomeState extends State<CameraExampleHome>
|
||||
with WidgetsBindingObserver, TickerProviderStateMixin {
|
||||
CameraController? controller;
|
||||
XFile? imageFile;
|
||||
XFile? videoFile;
|
||||
VideoPlayerController? videoController;
|
||||
VoidCallback? videoPlayerListener;
|
||||
bool enableAudio = true;
|
||||
double _minAvailableExposureOffset = 0.0;
|
||||
double _maxAvailableExposureOffset = 0.0;
|
||||
double _currentExposureOffset = 0.0;
|
||||
late AnimationController _flashModeControlRowAnimationController;
|
||||
late Animation<double> _flashModeControlRowAnimation;
|
||||
late AnimationController _exposureModeControlRowAnimationController;
|
||||
late Animation<double> _exposureModeControlRowAnimation;
|
||||
late AnimationController _focusModeControlRowAnimationController;
|
||||
late Animation<double> _focusModeControlRowAnimation;
|
||||
double _minAvailableZoom = 1.0;
|
||||
double _maxAvailableZoom = 1.0;
|
||||
double _currentScale = 1.0;
|
||||
double _baseScale = 1.0;
|
||||
|
||||
// Counting pointers (number of user fingers on screen)
|
||||
int _pointers = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ambiguate(WidgetsBinding.instance)?.addObserver(this);
|
||||
|
||||
_flashModeControlRowAnimationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_flashModeControlRowAnimation = CurvedAnimation(
|
||||
parent: _flashModeControlRowAnimationController,
|
||||
curve: Curves.easeInCubic,
|
||||
);
|
||||
_exposureModeControlRowAnimationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_exposureModeControlRowAnimation = CurvedAnimation(
|
||||
parent: _exposureModeControlRowAnimationController,
|
||||
curve: Curves.easeInCubic,
|
||||
);
|
||||
_focusModeControlRowAnimationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_focusModeControlRowAnimation = CurvedAnimation(
|
||||
parent: _focusModeControlRowAnimationController,
|
||||
curve: Curves.easeInCubic,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ambiguate(WidgetsBinding.instance)?.removeObserver(this);
|
||||
_flashModeControlRowAnimationController.dispose();
|
||||
_exposureModeControlRowAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
// App state changed before we got the chance to initialize.
|
||||
if (cameraController == null || !cameraController.value.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == AppLifecycleState.inactive) {
|
||||
cameraController.dispose();
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
onNewCameraSelected(cameraController.description);
|
||||
}
|
||||
}
|
||||
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
title: const Text('Camera example'),
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(1.0),
|
||||
child: Center(
|
||||
child: _cameraPreviewWidget(),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
border: Border.all(
|
||||
color:
|
||||
controller != null && controller!.value.isRecordingVideo
|
||||
? Colors.redAccent
|
||||
: Colors.grey,
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_captureControlRowWidget(),
|
||||
_modeControlRowWidget(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_cameraTogglesRowWidget(),
|
||||
_thumbnailWidget(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Display the preview from the camera (or a message if the preview is not available).
|
||||
Widget _cameraPreviewWidget() {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
if (cameraController == null || !cameraController.value.isInitialized) {
|
||||
return const Text(
|
||||
'Tap a camera',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Listener(
|
||||
onPointerDown: (_) => _pointers++,
|
||||
onPointerUp: (_) => _pointers--,
|
||||
child: CameraPreview(
|
||||
controller!,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onScaleStart: _handleScaleStart,
|
||||
onScaleUpdate: _handleScaleUpdate,
|
||||
onTapDown: (details) => onViewFinderTap(details, constraints),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleScaleStart(ScaleStartDetails details) {
|
||||
_baseScale = _currentScale;
|
||||
}
|
||||
|
||||
Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
|
||||
// When there are not exactly two fingers on screen don't scale
|
||||
if (controller == null || _pointers != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
_currentScale = (_baseScale * details.scale)
|
||||
.clamp(_minAvailableZoom, _maxAvailableZoom);
|
||||
|
||||
await controller!.setZoomLevel(_currentScale);
|
||||
}
|
||||
|
||||
/// Display the thumbnail of the captured image or video.
|
||||
Widget _thumbnailWidget() {
|
||||
final VideoPlayerController? localVideoController = videoController;
|
||||
|
||||
return Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
localVideoController == null && imageFile == null
|
||||
? Container()
|
||||
: SizedBox(
|
||||
child: (localVideoController == null)
|
||||
? Image.file(File(imageFile!.path))
|
||||
: Container(
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio:
|
||||
localVideoController.value.size != null
|
||||
? localVideoController
|
||||
.value.aspectRatio
|
||||
: 1.0,
|
||||
child: VideoPlayer(localVideoController)),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.pink)),
|
||||
),
|
||||
width: 64.0,
|
||||
height: 64.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Display a bar with buttons to change the flash and exposure modes
|
||||
Widget _modeControlRowWidget() {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.flash_on),
|
||||
color: Colors.blue,
|
||||
onPressed: controller != null ? onFlashModeButtonPressed : null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.exposure),
|
||||
color: Colors.blue,
|
||||
onPressed:
|
||||
controller != null ? onExposureModeButtonPressed : null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.filter_center_focus),
|
||||
color: Colors.blue,
|
||||
onPressed: controller != null ? onFocusModeButtonPressed : null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
|
||||
color: Colors.blue,
|
||||
onPressed: controller != null ? onAudioModeButtonPressed : null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(controller?.value.isCaptureOrientationLocked ?? false
|
||||
? Icons.screen_lock_rotation
|
||||
: Icons.screen_rotation),
|
||||
color: Colors.blue,
|
||||
onPressed: controller != null
|
||||
? onCaptureOrientationLockButtonPressed
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
_flashModeControlRowWidget(),
|
||||
_exposureModeControlRowWidget(),
|
||||
_focusModeControlRowWidget(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _flashModeControlRowWidget() {
|
||||
return SizeTransition(
|
||||
sizeFactor: _flashModeControlRowAnimation,
|
||||
child: ClipRect(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.flash_off),
|
||||
color: controller?.value.flashMode == FlashMode.off
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
onPressed: controller != null
|
||||
? () => onSetFlashModeButtonPressed(FlashMode.off)
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.flash_auto),
|
||||
color: controller?.value.flashMode == FlashMode.auto
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
onPressed: controller != null
|
||||
? () => onSetFlashModeButtonPressed(FlashMode.auto)
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.flash_on),
|
||||
color: controller?.value.flashMode == FlashMode.always
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
onPressed: controller != null
|
||||
? () => onSetFlashModeButtonPressed(FlashMode.always)
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.highlight),
|
||||
color: controller?.value.flashMode == FlashMode.torch
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
onPressed: controller != null
|
||||
? () => onSetFlashModeButtonPressed(FlashMode.torch)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _exposureModeControlRowWidget() {
|
||||
final ButtonStyle styleAuto = TextButton.styleFrom(
|
||||
primary: controller?.value.exposureMode == ExposureMode.auto
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
);
|
||||
final ButtonStyle styleLocked = TextButton.styleFrom(
|
||||
primary: controller?.value.exposureMode == ExposureMode.locked
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
);
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: _exposureModeControlRowAnimation,
|
||||
child: ClipRect(
|
||||
child: Container(
|
||||
color: Colors.grey.shade50,
|
||||
child: Column(
|
||||
children: [
|
||||
Center(
|
||||
child: Text("Exposure Mode"),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
TextButton(
|
||||
child: Text('AUTO'),
|
||||
style: styleAuto,
|
||||
onPressed: controller != null
|
||||
? () =>
|
||||
onSetExposureModeButtonPressed(ExposureMode.auto)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
if (controller != null) {
|
||||
controller!.setExposurePoint(null);
|
||||
showInSnackBar('Resetting exposure point');
|
||||
}
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('LOCKED'),
|
||||
style: styleLocked,
|
||||
onPressed: controller != null
|
||||
? () =>
|
||||
onSetExposureModeButtonPressed(ExposureMode.locked)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Text("Exposure Offset"),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(_minAvailableExposureOffset.toString()),
|
||||
Slider(
|
||||
value: _currentExposureOffset,
|
||||
min: _minAvailableExposureOffset,
|
||||
max: _maxAvailableExposureOffset,
|
||||
label: _currentExposureOffset.toString(),
|
||||
onChanged: _minAvailableExposureOffset ==
|
||||
_maxAvailableExposureOffset
|
||||
? null
|
||||
: setExposureOffset,
|
||||
),
|
||||
Text(_maxAvailableExposureOffset.toString()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _focusModeControlRowWidget() {
|
||||
final ButtonStyle styleAuto = TextButton.styleFrom(
|
||||
primary: controller?.value.focusMode == FocusMode.auto
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
);
|
||||
final ButtonStyle styleLocked = TextButton.styleFrom(
|
||||
primary: controller?.value.focusMode == FocusMode.locked
|
||||
? Colors.orange
|
||||
: Colors.blue,
|
||||
);
|
||||
|
||||
return SizeTransition(
|
||||
sizeFactor: _focusModeControlRowAnimation,
|
||||
child: ClipRect(
|
||||
child: Container(
|
||||
color: Colors.grey.shade50,
|
||||
child: Column(
|
||||
children: [
|
||||
Center(
|
||||
child: Text("Focus Mode"),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
TextButton(
|
||||
child: Text('AUTO'),
|
||||
style: styleAuto,
|
||||
onPressed: controller != null
|
||||
? () => onSetFocusModeButtonPressed(FocusMode.auto)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
if (controller != null) controller!.setFocusPoint(null);
|
||||
showInSnackBar('Resetting focus point');
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('LOCKED'),
|
||||
style: styleLocked,
|
||||
onPressed: controller != null
|
||||
? () => onSetFocusModeButtonPressed(FocusMode.locked)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Display the control bar with buttons to take pictures and record videos.
|
||||
Widget _captureControlRowWidget() {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
color: Colors.blue,
|
||||
onPressed: cameraController != null &&
|
||||
cameraController.value.isInitialized &&
|
||||
!cameraController.value.isRecordingVideo
|
||||
? onTakePictureButtonPressed
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.videocam),
|
||||
color: Colors.blue,
|
||||
onPressed: cameraController != null &&
|
||||
cameraController.value.isInitialized &&
|
||||
!cameraController.value.isRecordingVideo
|
||||
? onVideoRecordButtonPressed
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: cameraController != null &&
|
||||
cameraController.value.isRecordingPaused
|
||||
? Icon(Icons.play_arrow)
|
||||
: Icon(Icons.pause),
|
||||
color: Colors.blue,
|
||||
onPressed: cameraController != null &&
|
||||
cameraController.value.isInitialized &&
|
||||
cameraController.value.isRecordingVideo
|
||||
? (cameraController.value.isRecordingPaused)
|
||||
? onResumeButtonPressed
|
||||
: onPauseButtonPressed
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.stop),
|
||||
color: Colors.red,
|
||||
onPressed: cameraController != null &&
|
||||
cameraController.value.isInitialized &&
|
||||
cameraController.value.isRecordingVideo
|
||||
? onStopButtonPressed
|
||||
: null,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Display a row of toggle to select the camera (or a message if no camera is available).
|
||||
Widget _cameraTogglesRowWidget() {
|
||||
final List<Widget> toggles = <Widget>[];
|
||||
|
||||
final onChanged = (CameraDescription? description) {
|
||||
if (description == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
onNewCameraSelected(description);
|
||||
};
|
||||
|
||||
if (cameras.isEmpty) {
|
||||
return const Text('No camera found');
|
||||
} else {
|
||||
for (CameraDescription cameraDescription in cameras) {
|
||||
toggles.add(
|
||||
SizedBox(
|
||||
width: 90.0,
|
||||
child: RadioListTile<CameraDescription>(
|
||||
title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
|
||||
groupValue: controller?.description,
|
||||
value: cameraDescription,
|
||||
onChanged:
|
||||
controller != null && controller!.value.isRecordingVideo
|
||||
? null
|
||||
: onChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Row(children: toggles);
|
||||
}
|
||||
|
||||
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
|
||||
|
||||
void showInSnackBar(String message) {
|
||||
// ignore: deprecated_member_use
|
||||
_scaffoldKey.currentState?.showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
|
||||
void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CameraController cameraController = controller!;
|
||||
|
||||
final offset = Offset(
|
||||
details.localPosition.dx / constraints.maxWidth,
|
||||
details.localPosition.dy / constraints.maxHeight,
|
||||
);
|
||||
cameraController.setExposurePoint(offset);
|
||||
cameraController.setFocusPoint(offset);
|
||||
}
|
||||
|
||||
void onNewCameraSelected(CameraDescription cameraDescription) async {
|
||||
if (controller != null) {
|
||||
await controller!.dispose();
|
||||
}
|
||||
final CameraController cameraController = CameraController(
|
||||
cameraDescription,
|
||||
ResolutionPreset.medium,
|
||||
enableAudio: enableAudio,
|
||||
imageFormatGroup: ImageFormatGroup.jpeg,
|
||||
);
|
||||
controller = cameraController;
|
||||
|
||||
// If the controller is updated then update the UI.
|
||||
cameraController.addListener(() {
|
||||
if (mounted) setState(() {});
|
||||
if (cameraController.value.hasError) {
|
||||
showInSnackBar(
|
||||
'Camera error ${cameraController.value.errorDescription}');
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await cameraController.initialize();
|
||||
await Future.wait([
|
||||
cameraController
|
||||
.getMinExposureOffset()
|
||||
.then((value) => _minAvailableExposureOffset = value),
|
||||
cameraController
|
||||
.getMaxExposureOffset()
|
||||
.then((value) => _maxAvailableExposureOffset = value),
|
||||
cameraController
|
||||
.getMaxZoomLevel()
|
||||
.then((value) => _maxAvailableZoom = value),
|
||||
cameraController
|
||||
.getMinZoomLevel()
|
||||
.then((value) => _minAvailableZoom = value),
|
||||
]);
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void onTakePictureButtonPressed() {
|
||||
takePicture().then((XFile? file) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
imageFile = file;
|
||||
videoController?.dispose();
|
||||
videoController = null;
|
||||
});
|
||||
if (file != null) showInSnackBar('Picture saved to ${file.path}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onFlashModeButtonPressed() {
|
||||
if (_flashModeControlRowAnimationController.value == 1) {
|
||||
_flashModeControlRowAnimationController.reverse();
|
||||
} else {
|
||||
_flashModeControlRowAnimationController.forward();
|
||||
_exposureModeControlRowAnimationController.reverse();
|
||||
_focusModeControlRowAnimationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void onExposureModeButtonPressed() {
|
||||
if (_exposureModeControlRowAnimationController.value == 1) {
|
||||
_exposureModeControlRowAnimationController.reverse();
|
||||
} else {
|
||||
_exposureModeControlRowAnimationController.forward();
|
||||
_flashModeControlRowAnimationController.reverse();
|
||||
_focusModeControlRowAnimationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void onFocusModeButtonPressed() {
|
||||
if (_focusModeControlRowAnimationController.value == 1) {
|
||||
_focusModeControlRowAnimationController.reverse();
|
||||
} else {
|
||||
_focusModeControlRowAnimationController.forward();
|
||||
_flashModeControlRowAnimationController.reverse();
|
||||
_exposureModeControlRowAnimationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void onAudioModeButtonPressed() {
|
||||
enableAudio = !enableAudio;
|
||||
if (controller != null) {
|
||||
onNewCameraSelected(controller!.description);
|
||||
}
|
||||
}
|
||||
|
||||
void onCaptureOrientationLockButtonPressed() async {
|
||||
if (controller != null) {
|
||||
final CameraController cameraController = controller!;
|
||||
if (cameraController.value.isCaptureOrientationLocked) {
|
||||
await cameraController.unlockCaptureOrientation();
|
||||
showInSnackBar('Capture orientation unlocked');
|
||||
} else {
|
||||
await cameraController.lockCaptureOrientation();
|
||||
showInSnackBar(
|
||||
'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onSetFlashModeButtonPressed(FlashMode mode) {
|
||||
setFlashMode(mode).then((_) {
|
||||
if (mounted) setState(() {});
|
||||
showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');
|
||||
});
|
||||
}
|
||||
|
||||
void onSetExposureModeButtonPressed(ExposureMode mode) {
|
||||
setExposureMode(mode).then((_) {
|
||||
if (mounted) setState(() {});
|
||||
showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}');
|
||||
});
|
||||
}
|
||||
|
||||
void onSetFocusModeButtonPressed(FocusMode mode) {
|
||||
setFocusMode(mode).then((_) {
|
||||
if (mounted) setState(() {});
|
||||
showInSnackBar('Focus mode set to ${mode.toString().split('.').last}');
|
||||
});
|
||||
}
|
||||
|
||||
void onVideoRecordButtonPressed() {
|
||||
startVideoRecording().then((_) {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
void onStopButtonPressed() {
|
||||
stopVideoRecording().then((file) {
|
||||
if (mounted) setState(() {});
|
||||
if (file != null) {
|
||||
showInSnackBar('Video recorded to ${file.path}');
|
||||
videoFile = file;
|
||||
_startVideoPlayer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onPauseButtonPressed() {
|
||||
pauseVideoRecording().then((_) {
|
||||
if (mounted) setState(() {});
|
||||
showInSnackBar('Video recording paused');
|
||||
});
|
||||
}
|
||||
|
||||
void onResumeButtonPressed() {
|
||||
resumeVideoRecording().then((_) {
|
||||
if (mounted) setState(() {});
|
||||
showInSnackBar('Video recording resumed');
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> startVideoRecording() async {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
if (cameraController == null || !cameraController.value.isInitialized) {
|
||||
showInSnackBar('Error: select a camera first.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (cameraController.value.isRecordingVideo) {
|
||||
// A recording is already started, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await cameraController.startVideoRecording();
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<XFile?> stopVideoRecording() async {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
if (cameraController == null || !cameraController.value.isRecordingVideo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return cameraController.stopVideoRecording();
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pauseVideoRecording() async {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
if (cameraController == null || !cameraController.value.isRecordingVideo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
await cameraController.pauseVideoRecording();
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resumeVideoRecording() async {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
if (cameraController == null || !cameraController.value.isRecordingVideo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
await cameraController.resumeVideoRecording();
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setFlashMode(FlashMode mode) async {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await controller!.setFlashMode(mode);
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setExposureMode(ExposureMode mode) async {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await controller!.setExposureMode(mode);
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setExposureOffset(double offset) async {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_currentExposureOffset = offset;
|
||||
});
|
||||
try {
|
||||
offset = await controller!.setExposureOffset(offset);
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setFocusMode(FocusMode mode) async {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await controller!.setFocusMode(mode);
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startVideoPlayer() async {
|
||||
if (videoFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final VideoPlayerController vController =
|
||||
VideoPlayerController.file(File(videoFile!.path));
|
||||
videoPlayerListener = () {
|
||||
if (videoController != null && videoController!.value.size != null) {
|
||||
// Refreshing the state to update video player with the correct ratio.
|
||||
if (mounted) setState(() {});
|
||||
videoController!.removeListener(videoPlayerListener!);
|
||||
}
|
||||
};
|
||||
vController.addListener(videoPlayerListener!);
|
||||
await vController.setLooping(true);
|
||||
await vController.initialize();
|
||||
await videoController?.dispose();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
imageFile = null;
|
||||
videoController = vController;
|
||||
});
|
||||
}
|
||||
await vController.play();
|
||||
}
|
||||
|
||||
Future<XFile?> takePicture() async {
|
||||
final CameraController? cameraController = controller;
|
||||
if (cameraController == null || !cameraController.value.isInitialized) {
|
||||
showInSnackBar('Error: select a camera first.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cameraController.value.isTakingPicture) {
|
||||
// A capture is already pending, do nothing.
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
XFile file = await cameraController.takePicture();
|
||||
return file;
|
||||
} on CameraException catch (e) {
|
||||
_showCameraException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _showCameraException(CameraException e) {
|
||||
logError(e.code, e.description);
|
||||
showInSnackBar('Error: ${e.code}\n${e.description}');
|
||||
}
|
||||
}
|
||||
|
||||
class CameraApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
scrollBehavior: MyCustomScrollBehavior(),
|
||||
home: CameraExampleHome(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyCustomScrollBehavior extends MaterialScrollBehavior {
|
||||
// Override behavior methods and getters like dragDevices
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
};
|
||||
}
|
||||
|
||||
List<CameraDescription> cameras = [];
|
||||
|
||||
Future<void> main() async {
|
||||
// Fetch the available cameras before initializing the app.
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
cameras = await availableCameras();
|
||||
} on CameraException catch (e) {
|
||||
logError(e.code, e.description);
|
||||
}
|
||||
runApp(CameraApp());
|
||||
}
|
||||
|
||||
/// This allows a value of type T or T? to be treated as a value of type T?.
|
||||
///
|
||||
/// We use this so that APIs that have become non-nullable can still be used
|
||||
/// with `!` and `?` on the stable branch.
|
||||
// TODO(ianh): Remove this once we roll stable in late 2021.
|
||||
T? _ambiguate<T>(T? value) => value;
|
Reference in New Issue
Block a user