137 lines
3.9 KiB
Dart
137 lines
3.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
class CustomVideoProgressBar extends StatefulWidget {
|
|
final VideoPlayerController controller;
|
|
|
|
final double? width;
|
|
final bool canSlide;
|
|
|
|
const CustomVideoProgressBar({
|
|
super.key,
|
|
required this.controller,
|
|
this.width,
|
|
this.canSlide = true,
|
|
});
|
|
|
|
@override
|
|
State<CustomVideoProgressBar> createState() => _CustomVideoProgressBarState();
|
|
}
|
|
|
|
class _CustomVideoProgressBarState extends State<CustomVideoProgressBar> {
|
|
bool _isDragging = false;
|
|
double? _dragValue;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
widget.controller.addListener(_updateState);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.controller.removeListener(_updateState);
|
|
super.dispose();
|
|
}
|
|
|
|
void _updateState() {
|
|
if (!_isDragging) {
|
|
if (mounted) setState(() {});
|
|
}
|
|
}
|
|
|
|
String _formatDuration(Duration duration) {
|
|
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
|
|
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
|
|
return '$minutes:$seconds';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final Duration duration = widget.controller.value.duration;
|
|
final Duration position = _isDragging
|
|
? Duration(milliseconds: _dragValue?.toInt() ?? 0)
|
|
: widget.controller.value.position;
|
|
|
|
return Column(
|
|
children: [
|
|
// 时间显示
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
'${_formatDuration(position)}/',
|
|
style: TextStyle(color: Color(0xFFDFDFDF), fontSize: 10.sp),
|
|
),
|
|
Text(
|
|
_formatDuration(duration),
|
|
style: TextStyle(color: Color(0xFFDFDFDF), fontSize: 10.sp),
|
|
),
|
|
],
|
|
),
|
|
// 进度条
|
|
SizedBox(
|
|
width: widget.width ?? ScreenUtil().screenWidth - 30.w,
|
|
child: SliderTheme(
|
|
data: SliderTheme.of(context).copyWith(
|
|
trackHeight: 2.0.w,
|
|
trackShape: UniformTrackShape(),
|
|
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6.0),
|
|
),
|
|
child: Slider(
|
|
padding: EdgeInsets.zero,
|
|
min: 0,
|
|
max: duration.inMilliseconds.toDouble(),
|
|
value: position.inMilliseconds.toDouble().clamp(
|
|
0,
|
|
duration.inMilliseconds.toDouble(),
|
|
),
|
|
|
|
onChangeStart: (_) {
|
|
if (!widget.canSlide) return;
|
|
_isDragging = true;
|
|
},
|
|
onChangeEnd: (value) {
|
|
if (!widget.canSlide) return;
|
|
_isDragging = false;
|
|
widget.controller.seekTo(Duration(milliseconds: value.toInt()));
|
|
widget.controller.play();
|
|
_dragValue = null;
|
|
},
|
|
onChanged: (value) {
|
|
if (!widget.canSlide) return;
|
|
setState(() => _dragValue = value);
|
|
},
|
|
activeColor: Colors.white,
|
|
inactiveColor: Colors.white.withValues(alpha: .3),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// 自定义轨道形状,确保选中和未选中部分高度一致
|
|
class UniformTrackShape extends RoundedRectSliderTrackShape {
|
|
const UniformTrackShape();
|
|
|
|
@override
|
|
Rect getPreferredRect({
|
|
required RenderBox parentBox,
|
|
Offset offset = Offset.zero,
|
|
required SliderThemeData sliderTheme,
|
|
bool isEnabled = false,
|
|
bool isDiscrete = false,
|
|
}) {
|
|
final double? trackHeight = sliderTheme.trackHeight;
|
|
final double trackLeft = offset.dx;
|
|
final double trackTop =
|
|
offset.dy + (parentBox.size.height - trackHeight!) / 2;
|
|
final double trackWidth = parentBox.size.width;
|
|
|
|
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
|
|
}
|
|
}
|