@@ -12,6 +12,7 @@ import 'package:flutter/gestures.dart';
12
12
import 'package:flutter/material.dart' ;
13
13
import 'package:flutter/semantics.dart' ;
14
14
import 'package:flutter/services.dart' ;
15
+ import 'package:sensors_plus/sensors_plus.dart' ;
15
16
16
17
import '../constants/config.dart' ;
17
18
import '../constants/constants.dart' ;
@@ -208,19 +209,30 @@ class CameraPickerState extends State<CameraPicker>
208
209
final invalidControllerMethods = < CameraDescription , Set <String >> {};
209
210
bool retriedAfterInvalidInitialize = false ;
210
211
212
+ /// Subscribe to the accelerometer.
213
+ late final StreamSubscription <AccelerometerEvent > accelerometerSubscription;
214
+
215
+ /// The locked capture orientation of the current camera instance.
216
+ DeviceOrientation ? lockedCaptureOrientation;
217
+
211
218
@override
212
219
void initState () {
213
220
super .initState ();
214
221
ambiguate (WidgetsBinding .instance)? .addObserver (this );
215
222
Constants .textDelegate = widget.pickerConfig.textDelegate ??
216
223
cameraPickerTextDelegateFromLocale (widget.locale);
217
224
initCameras ();
225
+ accelerometerSubscription = accelerometerEvents.listen (
226
+ handleAccelerometerEvent,
227
+ );
218
228
}
219
229
220
230
@override
221
231
void dispose () {
222
232
ambiguate (WidgetsBinding .instance)? .removeObserver (this );
223
- innerController? .dispose ();
233
+ final c = innerController;
234
+ innerController = null ;
235
+ c? .dispose ();
224
236
currentExposureOffset.dispose ();
225
237
currentExposureSliderOffset.dispose ();
226
238
lastExposurePoint.dispose ();
@@ -231,6 +243,7 @@ class CameraPickerState extends State<CameraPicker>
231
243
exposureFadeOutTimer? .cancel ();
232
244
recordDetectTimer? .cancel ();
233
245
recordCountdownTimer? .cancel ();
246
+ accelerometerSubscription.cancel ();
234
247
super .dispose ();
235
248
}
236
249
@@ -322,6 +335,7 @@ class CameraPickerState extends State<CameraPicker>
322
335
lastExposurePoint.value = null ;
323
336
currentExposureOffset.value = 0 ;
324
337
currentExposureSliderOffset.value = 0 ;
338
+ lockedCaptureOrientation = pickerConfig.lockCaptureOrientation;
325
339
});
326
340
// **IMPORTANT**: Push methods into a post frame callback, which ensures the
327
341
// controller has already unbind from widgets.
@@ -467,6 +481,43 @@ class CameraPickerState extends State<CameraPicker>
467
481
});
468
482
}
469
483
484
+ /// Lock capture orientation according to the current status of the device,
485
+ /// which enables the captured file stored the correct orientation.
486
+ void handleAccelerometerEvent (AccelerometerEvent event) {
487
+ if (! mounted ||
488
+ pickerConfig.lockCaptureOrientation != null ||
489
+ innerController == null ||
490
+ ! controller.value.isInitialized ||
491
+ controller.value.isPreviewPaused ||
492
+ controller.value.isRecordingVideo ||
493
+ controller.value.isTakingPicture) {
494
+ return ;
495
+ }
496
+ final x = event.x, y = event.y, z = event.z;
497
+ final DeviceOrientation ? newOrientation;
498
+ if (x.abs () > y.abs () && x.abs () > z.abs ()) {
499
+ if (x > 0 ) {
500
+ newOrientation = DeviceOrientation .landscapeLeft;
501
+ } else {
502
+ newOrientation = DeviceOrientation .landscapeRight;
503
+ }
504
+ } else if (y.abs () > x.abs () && y.abs () > z.abs ()) {
505
+ if (y > 0 ) {
506
+ newOrientation = DeviceOrientation .portraitUp;
507
+ } else {
508
+ newOrientation = DeviceOrientation .portraitDown;
509
+ }
510
+ } else {
511
+ newOrientation = null ;
512
+ }
513
+ // Throttle.
514
+ if (newOrientation != null && lockedCaptureOrientation != newOrientation) {
515
+ lockedCaptureOrientation = newOrientation;
516
+ realDebugPrint ('Locking new capture orientation: $newOrientation ' );
517
+ controller.lockCaptureOrientation (newOrientation);
518
+ }
519
+ }
520
+
470
521
/// Initializes the flash modes in [validFlashModes] for each
471
522
/// [CameraDescription] .
472
523
/// 为每个 [CameraDescription] 在 [validFlashModes] 中初始化闪光灯模式。
@@ -1542,7 +1593,29 @@ class CameraPickerState extends State<CameraPicker>
1542
1593
required CameraValue cameraValue,
1543
1594
required BoxConstraints constraints,
1544
1595
}) {
1545
- Widget preview = Listener (
1596
+ Widget preview = const SizedBox .shrink ();
1597
+ if (innerController != null ) {
1598
+ preview = CameraPreview (controller);
1599
+ preview = ValueListenableBuilder <CameraValue >(
1600
+ valueListenable: controller,
1601
+ builder: (_, CameraValue value, Widget ? child) {
1602
+ final lockedOrientation = value.lockedCaptureOrientation;
1603
+ int ? quarterTurns = lockedOrientation? .index;
1604
+ if (quarterTurns == null ) {
1605
+ return child! ;
1606
+ }
1607
+ if (value.deviceOrientation == DeviceOrientation .landscapeLeft) {
1608
+ quarterTurns-- ;
1609
+ } else if (value.deviceOrientation ==
1610
+ DeviceOrientation .landscapeRight) {
1611
+ quarterTurns++ ;
1612
+ }
1613
+ return RotatedBox (quarterTurns: quarterTurns, child: child);
1614
+ },
1615
+ child: preview,
1616
+ );
1617
+ }
1618
+ preview = Listener (
1546
1619
onPointerDown: (_) => pointers++ ,
1547
1620
onPointerUp: (_) => pointers-- ,
1548
1621
child: GestureDetector (
@@ -1551,9 +1624,7 @@ class CameraPickerState extends State<CameraPicker>
1551
1624
pickerConfig.enablePinchToZoom ? handleScaleUpdate : null ,
1552
1625
// Enabled cameras switching by default if we have multiple cameras.
1553
1626
onDoubleTap: cameras.length > 1 ? switchCameras : null ,
1554
- child: innerController != null
1555
- ? CameraPreview (controller)
1556
- : const SizedBox .shrink (),
1627
+ child: preview,
1557
1628
),
1558
1629
);
1559
1630
@@ -1568,7 +1639,6 @@ class CameraPickerState extends State<CameraPicker>
1568
1639
preview = Stack (
1569
1640
children: < Widget > [
1570
1641
preview,
1571
- // Image.asset('assets/1.jpg', fit: BoxFit.cover),
1572
1642
Positioned .fill (
1573
1643
child: ExcludeSemantics (
1574
1644
child: RotatedBox (
0 commit comments