Skip to content

Commit 2203168

Browse files
authored
🐛 Fix various of problems with the capture button (#219)
1 parent 5370cca commit 2203168

File tree

4 files changed

+79
-100
lines changed

4 files changed

+79
-100
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ See the [Migration Guide](guides/migration_guide.md) for the details of breaking
1515
### Fixes
1616

1717
- Handle exceptions after all flows.
18+
- Fix various of problems with the capture button.
1819

1920
## 4.0.3
2021

lib/src/states/camera_picker_state.dart

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ class CameraPickerState extends State<CameraPicker>
5757
/// 可用的相机实例
5858
late List<CameraDescription> cameras;
5959

60-
/// Whether the controller is handling taking picture or recording video.
61-
/// 相机控制器是否在处理拍照或录像
60+
/// Whether the controller is handling method calls.
61+
/// 相机控制器是否在处理方法调用
6262
bool isControllerBusy = false;
6363

6464
/// Current exposure offset.
@@ -186,6 +186,12 @@ class CameraPickerState extends State<CameraPicker>
186186
return pickerConfig.minimumRecordingDuration;
187187
}
188188

189+
/// Whether the capture button is displaying.
190+
bool get shouldCaptureButtonDisplay =>
191+
isControllerBusy ||
192+
(innerController?.value.isRecordingVideo ?? false) &&
193+
isRecordingRestricted;
194+
189195
/// Whether the camera preview should be rotated.
190196
bool get isCameraRotated => pickerConfig.cameraQuarterTurns % 4 != 0;
191197

@@ -258,15 +264,19 @@ class CameraPickerState extends State<CameraPicker>
258264
} else if (state == AppLifecycleState.inactive) {
259265
c.dispose();
260266
innerController = null;
267+
isControllerBusy = false;
261268
}
262269
}
263270

264271
/// Adjust the proper scale type according to the [constraints].
265272
/// 根据 [constraints] 获取相机预览适用的缩放。
266273
double effectiveCameraScale(
267274
BoxConstraints constraints,
268-
CameraController controller,
275+
CameraController? controller,
269276
) {
277+
if (controller == null) {
278+
return 1;
279+
}
270280
final int turns = cameraQuarterTurns;
271281
final String orientation = controller.value.deviceOrientation.toString();
272282
// Fetch the biggest size from the constraints.
@@ -831,7 +841,10 @@ class CameraPickerState extends State<CameraPicker>
831841
if (isControllerBusy) {
832842
return;
833843
}
834-
isControllerBusy = true;
844+
setState(() {
845+
isControllerBusy = true;
846+
isShootingButtonAnimate = true;
847+
});
835848
final ExposureMode previousExposureMode = controller.value.exposureMode;
836849
try {
837850
await Future.wait(<Future<void>>[
@@ -881,8 +894,10 @@ class CameraPickerState extends State<CameraPicker>
881894
} catch (e, s) {
882895
handleErrorWithHandler(e, s, pickerConfig.onError);
883896
} finally {
884-
isControllerBusy = false;
885-
safeSetState(() {});
897+
safeSetState(() {
898+
isControllerBusy = false;
899+
isShootingButtonAnimate = false;
900+
});
886901
}
887902
}
888903

@@ -909,14 +924,7 @@ class CameraPickerState extends State<CameraPicker>
909924
/// 将被取消,并且状态会重置。
910925
void recordDetectionCancel(PointerUpEvent event) {
911926
recordDetectTimer?.cancel();
912-
if (isShootingButtonAnimate) {
913-
safeSetState(() {
914-
isShootingButtonAnimate = false;
915-
});
916-
}
917927
if (innerController?.value.isRecordingVideo == true) {
918-
lastShootingButtonPressedPosition = null;
919-
safeSetState(() {});
920928
stopRecordingVideo();
921929
}
922930
}
@@ -940,7 +948,6 @@ class CameraPickerState extends State<CameraPicker>
940948
..reset()
941949
..start();
942950
} catch (e, s) {
943-
isControllerBusy = false;
944951
if (!controller.value.isRecordingVideo) {
945952
handleErrorWithHandler(e, s, pickerConfig.onError);
946953
return;
@@ -955,34 +962,39 @@ class CameraPickerState extends State<CameraPicker>
955962
recordStopwatch.stop();
956963
}
957964
} finally {
958-
safeSetState(() {});
965+
safeSetState(() {
966+
isControllerBusy = false;
967+
});
959968
}
960969
}
961970

962971
/// Stop the recording process.
963972
/// 停止录制视频
964973
Future<void> stopRecordingVideo() async {
965-
void handleError() {
966-
recordCountdownTimer?.cancel();
967-
isShootingButtonAnimate = false;
968-
safeSetState(() {});
974+
if (isControllerBusy) {
975+
return;
969976
}
970977

971978
recordStopwatch.stop();
972-
if (!controller.value.isRecordingVideo) {
973-
handleError();
979+
if (innerController == null || !controller.value.isRecordingVideo) {
980+
recordCountdownTimer?.cancel();
981+
safeSetState(() {
982+
isControllerBusy = false;
983+
isShootingButtonAnimate = false;
984+
});
974985
return;
975986
}
976987
safeSetState(() {
977-
isShootingButtonAnimate = false;
988+
isControllerBusy = true;
989+
lastShootingButtonPressedPosition = null;
978990
});
979991
try {
980992
final XFile file = await controller.stopVideoRecording();
981993
if (recordStopwatch.elapsed < minimumRecordingDuration) {
982994
pickerConfig.onMinimumRecordDurationNotMet?.call();
983995
return;
984996
}
985-
await controller.pausePreview();
997+
controller.pausePreview();
986998
final bool? isCapturedFileHandled = pickerConfig.onXFileCaptured?.call(
987999
file,
9881000
CameraPickerViewType.video,
@@ -1000,12 +1012,14 @@ class CameraPickerState extends State<CameraPicker>
10001012
await controller.resumePreview();
10011013
}
10021014
} catch (e, s) {
1003-
handleError();
1015+
recordCountdownTimer?.cancel();
10041016
initCameras();
10051017
handleErrorWithHandler(e, s, pickerConfig.onError);
10061018
} finally {
1007-
isControllerBusy = false;
1008-
safeSetState(() {});
1019+
safeSetState(() {
1020+
isControllerBusy = false;
1021+
isShootingButtonAnimate = false;
1022+
});
10091023
}
10101024
}
10111025

@@ -1319,17 +1333,17 @@ class CameraPickerState extends State<CameraPicker>
13191333
),
13201334
),
13211335
),
1322-
if ((innerController?.value.isRecordingVideo ?? false) &&
1323-
isRecordingRestricted)
1336+
if (shouldCaptureButtonDisplay)
13241337
RotatedBox(
13251338
quarterTurns:
13261339
!enableScaledPreview ? cameraQuarterTurns : 0,
13271340
child: CameraProgressButton(
13281341
isAnimating: isShootingButtonAnimate,
1342+
isBusy: isControllerBusy,
13291343
duration: pickerConfig.maximumRecordingDuration!,
1330-
outerRadius: outerSize.width,
1344+
size: outerSize,
13311345
ringsColor: theme.indicatorColor,
1332-
ringsWidth: 2,
1346+
ringsWidth: 3,
13331347
),
13341348
),
13351349
],
@@ -1675,7 +1689,7 @@ class CameraPickerState extends State<CameraPicker>
16751689
// Scale the preview if the config is enabled.
16761690
if (enableScaledPreview) {
16771691
preview = Transform.scale(
1678-
scale: effectiveCameraScale(constraints, controller),
1692+
scale: effectiveCameraScale(constraints, innerController),
16791693
child: Center(child: transformedWidget ?? preview),
16801694
);
16811695
// Rotated the preview if the turns is valid.

lib/src/widgets/camera_progress_button.dart

Lines changed: 33 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Use of this source code is governed by an Apache license that can be found
33
// in the LICENSE file.
44

5-
import 'dart:math' as math;
6-
75
import 'package:flutter/material.dart';
86

97
import '../constants/styles.dart';
@@ -13,18 +11,18 @@ class CameraProgressButton extends StatefulWidget {
1311
const CameraProgressButton({
1412
super.key,
1513
required this.isAnimating,
16-
required this.outerRadius,
14+
required this.isBusy,
15+
required this.size,
1716
required this.ringsWidth,
1817
this.ringsColor = wechatThemeColor,
19-
this.progress = 0.0,
2018
this.duration = const Duration(seconds: 15),
2119
});
2220

2321
final bool isAnimating;
24-
final double outerRadius;
22+
final bool isBusy;
23+
final Size size;
2524
final double ringsWidth;
2625
final Color ringsColor;
27-
final double progress;
2826
final Duration duration;
2927

3028
@override
@@ -33,16 +31,15 @@ class CameraProgressButton extends StatefulWidget {
3331

3432
class _CircleProgressState extends State<CameraProgressButton>
3533
with SingleTickerProviderStateMixin {
36-
final GlobalKey paintKey = GlobalKey();
37-
38-
late final AnimationController progressController = AnimationController(
39-
duration: widget.duration,
40-
vsync: this,
41-
)..value = widget.progress;
34+
late final AnimationController progressController;
4235

4336
@override
4437
void initState() {
4538
super.initState();
39+
progressController = AnimationController(
40+
duration: widget.duration,
41+
vsync: this,
42+
);
4643
ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) {
4744
if (widget.isAnimating) {
4845
progressController.forward();
@@ -53,6 +50,18 @@ class _CircleProgressState extends State<CameraProgressButton>
5350
@override
5451
void didUpdateWidget(CameraProgressButton oldWidget) {
5552
super.didUpdateWidget(oldWidget);
53+
if (widget.isBusy != oldWidget.isBusy) {
54+
if (widget.isBusy) {
55+
progressController
56+
..reset()
57+
..stop();
58+
} else {
59+
progressController.value = 0.0;
60+
if (!progressController.isAnimating) {
61+
progressController.forward();
62+
}
63+
}
64+
}
5665
if (widget.isAnimating != oldWidget.isAnimating) {
5766
if (widget.isAnimating) {
5867
progressController.forward();
@@ -70,67 +79,23 @@ class _CircleProgressState extends State<CameraProgressButton>
7079

7180
@override
7281
Widget build(BuildContext context) {
73-
final Size size = Size.square(widget.outerRadius * 2);
82+
if (!widget.isAnimating && !widget.isBusy) {
83+
return const SizedBox.shrink();
84+
}
7485
return Center(
75-
child: RepaintBoundary(
76-
child: AnimatedBuilder(
77-
animation: progressController,
78-
builder: (_, __) => CustomPaint(
79-
key: paintKey,
80-
size: size,
81-
painter: CameraProgressButtonPainter(
82-
progress: progressController.value,
83-
ringsWidth: widget.ringsWidth,
84-
ringsColor: widget.ringsColor,
86+
child: SizedBox.fromSize(
87+
size: widget.size,
88+
child: RepaintBoundary(
89+
child: AnimatedBuilder(
90+
animation: progressController,
91+
builder: (_, __) => CircularProgressIndicator(
92+
color: widget.ringsColor,
93+
strokeWidth: widget.ringsWidth,
94+
value: widget.isBusy ? null : progressController.value,
8595
),
8696
),
8797
),
8898
),
8999
);
90100
}
91101
}
92-
93-
class CameraProgressButtonPainter extends CustomPainter {
94-
const CameraProgressButtonPainter({
95-
required this.ringsWidth,
96-
required this.ringsColor,
97-
required this.progress,
98-
});
99-
100-
final double ringsWidth;
101-
final Color ringsColor;
102-
final double progress;
103-
104-
@override
105-
void paint(Canvas canvas, Size size) {
106-
final double center = size.width / 2;
107-
final Offset offsetCenter = Offset(center, center);
108-
final double drawRadius = size.width / 2 - ringsWidth;
109-
110-
final double outerRadius = center;
111-
final double innerRadius = center - ringsWidth * 2;
112-
113-
final double progressWidth = outerRadius - innerRadius;
114-
canvas.save();
115-
canvas.translate(0.0, size.width);
116-
canvas.rotate(-math.pi / 2);
117-
final Rect arcRect = Rect.fromCircle(
118-
center: offsetCenter,
119-
radius: drawRadius,
120-
);
121-
final Paint progressPaint = Paint()
122-
..color = ringsColor
123-
..style = PaintingStyle.stroke
124-
..strokeWidth = progressWidth;
125-
canvas
126-
..drawArc(arcRect, 0, math.pi * 2 * progress, false, progressPaint)
127-
..restore();
128-
}
129-
130-
@override
131-
bool shouldRepaint(CameraProgressButtonPainter oldDelegate) {
132-
return oldDelegate.ringsWidth != ringsWidth ||
133-
oldDelegate.ringsColor != ringsColor ||
134-
oldDelegate.progress != progress;
135-
}
136-
}

lib/wechat_camera_picker.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,3 @@ export 'src/widgets/camera_focus_point.dart';
2020
export 'src/widgets/camera_picker.dart';
2121
export 'src/widgets/camera_picker_page_route.dart';
2222
export 'src/widgets/camera_picker_viewer.dart';
23-
export 'src/widgets/camera_progress_button.dart';

0 commit comments

Comments
 (0)