更换 录制按钮 UI

This commit is contained in:
2026-06-05 18:29:49 +08:00
parent 1e08b70c39
commit 26098114d2
3 changed files with 171 additions and 51 deletions

View File

@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// 录制控制按钮:白圈 + 红色内芯,录制中为圆角矩形。
class RecordingControlButton extends StatelessWidget {
const RecordingControlButton({
super.key,
required this.isRecording,
required this.onTap,
this.enabled = true,
this.size,
});
final bool isRecording;
final VoidCallback? onTap;
final bool enabled;
final double? size;
@override
Widget build(BuildContext context) {
final buttonSize = size ?? 70.r;
final borderWidth = 4.r;
final idleInnerSize = 62.r;
final recordingInnerSize = 22.r;
final recordingCornerRadius = 6.r;
final innerSize = isRecording ? recordingInnerSize : idleInnerSize;
final borderRadius = isRecording
? recordingCornerRadius
: idleInnerSize / 2;
return GestureDetector(
onTap: enabled ? onTap : null,
child: SizedBox(
width: buttonSize,
height: buttonSize,
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: buttonSize,
height: buttonSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: borderWidth),
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutCubic,
width: innerSize,
height: innerSize,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(borderRadius),
),
),
],
),
),
);
}
}

View File

@@ -4,6 +4,7 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:recording_tool/core/utils/rate_limiter.dart';
import 'package:recording_tool/features/recording/model/model_recording_session.dart';
import 'package:recording_tool/features/recording/widgets/widget_clipboard_address_clock_chip.dart';
import 'package:recording_tool/features/recording/widgets/widget_recording_button.dart';
import 'package:recording_tool/features/recording/widgets/widget_recording_setup_hints.dart';
/// 录制页 HUD 层(状态提示、录制控制)
@@ -112,10 +113,11 @@ class RecordingHudWidget extends StatelessWidget {
right: 0,
bottom: _recordButtonBottom,
child: Center(
child: GestureDetector(
onTap: state.isStartingRecording
? null
: () async {
child: RecordingControlButton(
isRecording: state.isRecording,
enabled: !state.isStartingRecording,
size: _recordButtonSize,
onTap: () {
if (state.isRecording) {
RateLimit.instance.debounce<void>(
key: 'recording.session.stop',
@@ -136,20 +138,6 @@ class RecordingHudWidget extends StatelessWidget {
);
}
},
child: Container(
width: _recordButtonSize,
height: _recordButtonSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 4.r),
color: state.isRecording ? Colors.white : Colors.red,
),
child: Icon(
state.isRecording ? Icons.stop : Icons.fiber_manual_record,
color: state.isRecording ? Colors.red : Colors.white,
size: 32.r,
),
),
),
),
),

View File

@@ -24,6 +24,7 @@ class _RecordingTouchLockOverlayWidgetState
extends State<RecordingTouchLockOverlayWidget> {
Timer? _holdTimer;
bool _isHolding = false;
int? _remainingSeconds;
@override
void didUpdateWidget(RecordingTouchLockOverlayWidget oldWidget) {
@@ -35,25 +36,48 @@ class _RecordingTouchLockOverlayWidgetState
@override
void dispose() {
_cancelHold();
_holdTimer?.cancel();
_holdTimer = null;
super.dispose();
}
void _cancelHold() {
_holdTimer?.cancel();
_holdTimer = null;
if (!_isHolding && _remainingSeconds == null) return;
setState(() {
_isHolding = false;
_remainingSeconds = null;
});
}
void _startHold() {
if (!widget.enabled) return;
setState(() => _isHolding = true);
final totalSeconds = widget.unlockHoldDuration.inSeconds;
_holdTimer?.cancel();
_holdTimer = Timer(widget.unlockHoldDuration, () {
if (!mounted) return;
_cancelHold();
setState(() {
_isHolding = true;
_remainingSeconds = totalSeconds;
});
var elapsed = 0;
_holdTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
elapsed += 1;
if (!mounted) {
timer.cancel();
return;
}
if (elapsed >= totalSeconds) {
timer.cancel();
_holdTimer = null;
widget.onUnlocked();
setState(() {});
setState(() {
_isHolding = false;
_remainingSeconds = null;
});
return;
}
setState(() => _remainingSeconds = totalSeconds - elapsed);
});
}
@@ -85,11 +109,56 @@ class _RecordingTouchLockOverlayWidgetState
horizontal: 16.r,
vertical: 8.r,
),
child: _isHolding && _remainingSeconds != null
? Builder(
builder: (context) {
final remainingSeconds = _remainingSeconds!;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 280),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: Tween<double>(begin: 0.6, end: 1)
.animate(animation),
child: FadeTransition(
opacity: animation,
child: child,
),
);
},
child: Text(
_isHolding
? '保持按住 ${widget.unlockHoldDuration.inSeconds}s 解锁…'
: '防误触已开启,按住 ${widget.unlockHoldDuration.inSeconds}s 解锁',
style: TextStyle(color: Colors.white, fontSize: 10.sp),
'${remainingSeconds}s',
key: ValueKey<int>(remainingSeconds),
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.w600,
height: 1.1,
),
),
),
SizedBox(height: 2.r),
Text(
'保持按住解锁',
style: TextStyle(
color: Colors.white70,
fontSize: 10.sp,
),
),
],
);
},
)
: Text(
'防误触已开启,按住 ${widget.unlockHoldDuration.inSeconds}s 解锁',
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
),
),
),
),