@@ -24,7 +24,6 @@ const Duration _kRouteDuration = Duration(milliseconds: 300);
24
24
/// 通过 [CameraDescription] 整合的拍照选择
25
25
///
26
26
/// The picker provides create an [AssetEntity] through the camera.
27
- ///
28
27
/// 该选择器可以通过拍照创建 [AssetEntity] 。
29
28
class CameraPicker extends StatefulWidget {
30
29
CameraPicker ({
@@ -71,7 +70,8 @@ class CameraPicker extends StatefulWidget {
71
70
/// The maximum duration of the video recording process.
72
71
/// 录制视频最长时长
73
72
///
74
- /// Defaults to 15 seconds, also allow `null` for unrestricted video recording.
73
+ /// Defaults to 15 seconds, allow `null` for unrestricted video recording.
74
+ /// 默认为 15 秒,可以使用 `null` 来设置无限制的视频录制
75
75
final Duration maximumRecordingDuration;
76
76
77
77
/// Theme data for the picker.
@@ -179,14 +179,20 @@ class CameraPickerState extends State<CameraPicker>
179
179
/// 最后一次手动聚焦的点坐标
180
180
final ValueNotifier <Offset > _lastExposurePoint = ValueNotifier <Offset >(null );
181
181
182
+ /// The [ValueNotifier] to keep the [CameraController] .
183
+ /// 用于保持 [CameraController] 的 [ValueNotifier]
184
+ final ValueNotifier <CameraController > _controllerNotifier =
185
+ ValueNotifier <CameraController >(null );
186
+
182
187
/// The controller for the current camera.
183
188
/// 当前相机实例的控制器
184
- CameraController controller;
189
+ CameraController get controller => _controllerNotifier.value ;
185
190
186
191
/// Available cameras.
187
192
/// 可用的相机实例
188
193
List <CameraDescription > cameras;
189
194
195
+ /// Current exposure offset.
190
196
/// 当前曝光值
191
197
final ValueNotifier <double > _currentExposureOffset =
192
198
ValueNotifier <double >(0.0 );
@@ -222,7 +228,6 @@ class CameraPickerState extends State<CameraPicker>
222
228
///
223
229
/// This happens when the [shootingButton] is being long pressed.
224
230
/// It will animate for video recording state.
225
- ///
226
231
/// 当长按拍照按钮时,会进入准备录制视频的状态,此时需要执行动画。
227
232
bool isShootingButtonAnimate = false ;
228
233
@@ -236,7 +241,6 @@ class CameraPickerState extends State<CameraPicker>
236
241
/// When the [shootingButton] started animate, this [Timer] will start
237
242
/// at the same time. When the time is more than [recordDetectDuration] ,
238
243
/// which means we should start recoding, the timer finished.
239
- ///
240
244
/// 当拍摄按钮开始执行动画时,定时器会同时启动。时长超过检测时长时,定时器完成。
241
245
Timer _recordDetectTimer;
242
246
@@ -245,7 +249,6 @@ class CameraPickerState extends State<CameraPicker>
245
249
///
246
250
/// Stop record When the record time reached the [maximumRecordingDuration] .
247
251
/// However, if there's no limitation on record time, this will be useless.
248
- ///
249
252
/// 当录像时间达到了最大时长,将通过定时器停止录像。
250
253
/// 但如果录像时间没有限制,定时器将不会起作用。
251
254
Timer _recordCountdownTimer;
@@ -254,11 +257,6 @@ class CameraPickerState extends State<CameraPicker>
254
257
////////////////////////////// Global Getters //////////////////////////////
255
258
////////////////////////////////////////////////////////////////////////////
256
259
257
- /// Whether the current [CameraDescription] initialized.
258
- /// 当前的相机实例是否已完成初始化
259
- bool get isInitialized =>
260
- controller != null && controller.value? .isInitialized == true ;
261
-
262
260
/// Whether the picker can record video. (A non-null wrapper)
263
261
/// 选择器是否可以录像(非空包装)
264
262
bool get isAllowRecording => widget.isAllowRecording ?? false ;
@@ -368,57 +366,71 @@ class CameraPickerState extends State<CameraPicker>
368
366
369
367
/// Initialize cameras instances.
370
368
/// 初始化相机实例
371
- Future <void > initCameras ([CameraDescription cameraDescription]) async {
372
- await controller? .dispose ();
369
+ void initCameras ([CameraDescription cameraDescription]) {
370
+ // Save the current controller to a local variable.
371
+ final CameraController _c = controller;
372
+ // Then unbind the controller from widgets, which requires a build frame.
373
+ setState (() {
374
+ _controllerNotifier.value = null ;
375
+ // Meanwhile, cancel the existed exposure point.
376
+ _exposurePointDisplayTimer? .cancel ();
377
+ _lastExposurePoint.value = null ;
378
+ });
379
+ // **IMPORTANT**: Push methods into a post frame callback, which ensures the
380
+ // controller has already unbind from widgets.
381
+ SchedulerBinding .instance.addPostFrameCallback ((_) async {
382
+ // Dispose at last to avoid disposed usage with assertions.
383
+ await _c? .dispose ();
384
+
385
+ // When the [cameraDescription] is null, which means this is the first
386
+ // time initializing cameras, so available cameras should be fetched.
387
+ if (cameraDescription == null ) {
388
+ cameras = await availableCameras ();
389
+ }
373
390
374
- /// When it's null, which means this is the first time initializing cameras.
375
- /// So cameras should fetch.
376
- if (cameraDescription == null ) {
377
- cameras = await availableCameras ();
378
- }
391
+ // After cameras fetched, judge again with the list is empty or not to
392
+ // ensure there is at least an available camera for use.
393
+ if (cameraDescription == null && (cameras? .isEmpty ?? true )) {
394
+ realDebugPrint ('No cameras found.' );
395
+ return ;
396
+ }
379
397
380
- /// After cameras fetched, judge again with the list is empty or not to
381
- /// ensure there is at least an available camera for use.
382
- if (cameraDescription == null && (cameras? .isEmpty ?? true )) {
383
- realDebugPrint ('No cameras found.' );
384
- return ;
385
- }
398
+ // Initialize the controller with the given resolution preset.
399
+ _controllerNotifier.value = CameraController (
400
+ cameraDescription ?? cameras[0 ],
401
+ widget.resolutionPreset,
402
+ enableAudio: enableAudio,
403
+ )..addListener (() {
404
+ if (controller.value.hasError) {
405
+ realDebugPrint ('Camera error ${controller .value .errorDescription }' );
406
+ }
407
+ });
386
408
387
- /// Initialize the controller with the given resolution preset.
388
- controller = CameraController (
389
- cameraDescription ?? cameras[0 ],
390
- widget.resolutionPreset,
391
- enableAudio: enableAudio,
392
- )..addListener (() {
409
+ try {
410
+ await controller.initialize ();
411
+ Future .wait <void >(< Future <dynamic >> [
412
+ (() async => _maxAvailableExposureOffset =
413
+ await controller.getMaxExposureOffset ())(),
414
+ (() async => _minAvailableExposureOffset =
415
+ await controller.getMinExposureOffset ())(),
416
+ (() async =>
417
+ _maxAvailableZoom = await controller.getMaxZoomLevel ())(),
418
+ (() async =>
419
+ _minAvailableZoom = await controller.getMinZoomLevel ())(),
420
+ ]);
421
+ } on CameraException catch (e) {
422
+ realDebugPrint ('CameraException: $e ' );
423
+ } finally {
393
424
safeSetState (() {});
394
- if (controller.value.hasError) {
395
- realDebugPrint ('Camera error ${controller .value .errorDescription }' );
396
- }
397
- });
398
-
399
- try {
400
- await controller.initialize ();
401
- Future .wait <void >(< Future <dynamic >> [
402
- (() async => _maxAvailableExposureOffset =
403
- await controller.getMaxExposureOffset ())(),
404
- (() async => _minAvailableExposureOffset =
405
- await controller.getMinExposureOffset ())(),
406
- (() async => _maxAvailableZoom = await controller.getMaxZoomLevel ())(),
407
- (() async => _minAvailableZoom = await controller.getMinZoomLevel ())(),
408
- ]);
409
- } on CameraException catch (e) {
410
- realDebugPrint ('CameraException: $e ' );
411
- } finally {
412
- safeSetState (() {});
413
- }
425
+ }
426
+ });
414
427
}
415
428
416
429
/// The method to switch cameras.
417
430
/// 切换相机的方法
418
431
///
419
432
/// Switch cameras in order. When the [currentCameraIndex] reached the length
420
433
/// of cameras, start from the beginning.
421
- ///
422
434
/// 按顺序切换相机。当达到相机数量时从头开始。
423
435
void switchCameras () {
424
436
++ currentCameraIndex;
@@ -493,7 +505,6 @@ class CameraPickerState extends State<CameraPicker>
493
505
///
494
506
/// The picture will only taken when [isInitialized] , and the camera is not
495
507
/// taking pictures.
496
- ///
497
508
/// 仅当初始化成功且相机未在拍照时拍照。
498
509
Future <void > takePicture () async {
499
510
if (controller.value.isInitialized && ! controller.value.isTakingPicture) {
@@ -520,7 +531,6 @@ class CameraPickerState extends State<CameraPicker>
520
531
/// will be initialized to achieve press time detection. If the duration
521
532
/// reached to same as [recordDetectDuration] , and the timer still active,
522
533
/// start recording video.
523
- ///
524
534
/// 当 [shootingButton] 触发了长按,初始化一个定时器来实现时间检测。如果长按时间
525
535
/// 达到了 [recordDetectDuration] 且定时器未被销毁,则开始录制视频。
526
536
void recordDetection () {
@@ -536,7 +546,6 @@ class CameraPickerState extends State<CameraPicker>
536
546
/// This will be given to the [Listener] in the [shootingButton] . When it's
537
547
/// called, which means no more pressing on the button, cancel the timer and
538
548
/// reset the status.
539
- ///
540
549
/// 这个方法会赋值给 [shootingButton] 中的 [Listener] 。当按钮释放了点击后,定时器
541
550
/// 将被取消,并且状态会重置。
542
551
void recordDetectionCancel (PointerUpEvent event) {
@@ -605,22 +614,21 @@ class CameraPickerState extends State<CameraPicker>
605
614
/// This displayed at the top of the screen.
606
615
/// 该区域显示在屏幕上方。
607
616
Widget get settingsAction {
608
- if (! isInitialized) {
609
- return const SizedBox .shrink ();
610
- }
611
- return Padding (
612
- padding: const EdgeInsets .symmetric (horizontal: 12.0 ),
613
- child: Row (
614
- children: < Widget > [const Spacer (), switchFlashesButton],
617
+ return _initializeWrapper (
618
+ builder: (CameraValue v, __) => Padding (
619
+ padding: const EdgeInsets .symmetric (horizontal: 12.0 ),
620
+ child: Row (
621
+ children: < Widget > [const Spacer (), switchFlashesButton (v)],
622
+ ),
615
623
),
616
624
);
617
625
}
618
626
619
627
/// The button to switch flash modes.
620
628
/// 切换闪光灯模式的按钮
621
- Widget get switchFlashesButton {
629
+ Widget switchFlashesButton ( CameraValue value) {
622
630
IconData icon;
623
- switch (controller. value.flashMode) {
631
+ switch (value.flashMode) {
624
632
case FlashMode .off:
625
633
icon = Icons .flash_off;
626
634
break ;
@@ -641,12 +649,9 @@ class CameraPickerState extends State<CameraPicker>
641
649
/// Text widget for shooting tips.
642
650
/// 拍摄的提示文字
643
651
Widget get tipsTextWidget {
644
- if (! isInitialized) {
645
- return const SizedBox .shrink ();
646
- }
647
652
return AnimatedOpacity (
648
653
duration: recordDetectDuration,
649
- opacity: controller.value.isRecordingVideo ? 0.0 : 1.0 ,
654
+ opacity: controller? .value? .isRecordingVideo ?? false ? 0.0 : 1.0 ,
650
655
child: Padding (
651
656
padding: const EdgeInsets .symmetric (
652
657
vertical: 20.0 ,
@@ -670,9 +675,9 @@ class CameraPickerState extends State<CameraPicker>
670
675
child: Row (
671
676
children: < Widget > [
672
677
Expanded (
673
- child: controller? .value? .isRecordingVideo == false
674
- ? Center (child : backButton )
675
- : const SizedBox . shrink ( ),
678
+ child: controller? .value? .isRecordingVideo == true
679
+ ? const SizedBox . shrink ( )
680
+ : Center (child : backButton ),
676
681
),
677
682
Expanded (child: Center (child: shootingButton)),
678
683
const Spacer (),
@@ -748,7 +753,7 @@ class CameraPickerState extends State<CameraPicker>
748
753
isInitialized: () =>
749
754
controller? .value? .isRecordingVideo == true &&
750
755
isRecordingRestricted,
751
- child : CircleProgressBar (
756
+ builder : (_, __) => CircleProgressBar (
752
757
duration: maximumRecordingDuration,
753
758
outerRadius: outerSize.width,
754
759
ringsWidth: 2.0 ,
@@ -796,7 +801,7 @@ class CameraPickerState extends State<CameraPicker>
796
801
);
797
802
}
798
803
799
- /// The [GestureDetector] widget for setting exposure poing manually.
804
+ /// The [GestureDetector] widget for setting exposure point manually.
800
805
/// 用于手动设置曝光点的 [GestureDetector]
801
806
Widget _exposureDetectorWidget (BuildContext context) {
802
807
return Positioned .fill (
@@ -859,15 +864,27 @@ class CameraPickerState extends State<CameraPicker>
859
864
}
860
865
861
866
Widget _initializeWrapper ({
862
- @required Widget child ,
867
+ @required Widget Function ( CameraValue , Widget ) builder ,
863
868
bool Function () isInitialized,
869
+ Widget child,
864
870
}) {
865
- assert (child != null );
866
- return AnimatedSwitcher (
867
- duration: kThemeAnimationDuration,
868
- child: isInitialized? .call () ?? this .isInitialized
869
- ? child
870
- : const SizedBox .shrink (),
871
+ assert (builder != null );
872
+ return ValueListenableBuilder <CameraController >(
873
+ valueListenable: _controllerNotifier,
874
+ builder: (_, CameraController controller, __) {
875
+ if (controller != null ) {
876
+ return ValueListenableBuilder <CameraValue >(
877
+ valueListenable: controller,
878
+ builder: (_, CameraValue value, Widget w) {
879
+ return isInitialized? .call () ?? value.isInitialized
880
+ ? builder (value, w)
881
+ : const SizedBox .shrink ();
882
+ },
883
+ child: child,
884
+ );
885
+ }
886
+ return const SizedBox .shrink ();
887
+ },
871
888
);
872
889
}
873
890
@@ -881,30 +898,40 @@ class CameraPickerState extends State<CameraPicker>
881
898
fit: StackFit .expand,
882
899
alignment: Alignment .center,
883
900
children: < Widget > [
884
- if (isInitialized)
885
- RotatedBox (
886
- quarterTurns: widget.cameraQuarterTurns ?? 0 ,
887
- child: AspectRatio (
888
- aspectRatio: controller.value.aspectRatio,
889
- child: Stack (
890
- children: < Widget > [
891
- Positioned .fill (child: _cameraPreview (context)),
892
- _focusingAreaWidget,
893
- ],
894
- ),
895
- ),
896
- ),
901
+ _initializeWrapper (
902
+ builder: (CameraValue value, __) {
903
+ if (value.isInitialized) {
904
+ return RotatedBox (
905
+ quarterTurns: widget.cameraQuarterTurns ?? 0 ,
906
+ child: AspectRatio (
907
+ aspectRatio: controller.value.aspectRatio,
908
+ child: RepaintBoundary (
909
+ child: Stack (
910
+ children: < Widget > [
911
+ Positioned .fill (child: _cameraPreview (context)),
912
+ _focusingAreaWidget,
913
+ ],
914
+ ),
915
+ ),
916
+ ),
917
+ );
918
+ }
919
+ return const SizedBox .expand ();},
920
+ ),
897
921
_exposureDetectorWidget (context),
898
922
SafeArea (
899
923
child: Padding (
900
924
padding: const EdgeInsets .only (bottom: 20.0 ),
901
- child: Column (
902
- children: < Widget > [
903
- settingsAction,
904
- const Spacer (),
905
- tipsTextWidget,
906
- shootingActions,
907
- ],
925
+ child: ValueListenableBuilder <CameraController >(
926
+ valueListenable: _controllerNotifier,
927
+ builder: (_, __, ___) => Column (
928
+ children: < Widget > [
929
+ settingsAction,
930
+ const Spacer (),
931
+ tipsTextWidget,
932
+ shootingActions,
933
+ ],
934
+ ),
908
935
),
909
936
),
910
937
),
0 commit comments