Flutter视频进度条拖动放大功能

166 阅读1分钟

g7go7-fchsj.gif

关键代码

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

class VideoProgressSlider extends StatefulWidget {
  final VideoPlayerController controller;
  final double defaultHeight;
  final double defaultThumbSize;
  final Color progressColor;
  final Color thumbColor;
  final Color trackColor;
  final double scale;

  const VideoProgressSlider({
    super.key,
    required this.controller,
    this.defaultHeight = 4.0,
    this.defaultThumbSize = 12.0,
    this.scale = 2,
    this.progressColor = const Color(0xFFFE2C55),
    this.thumbColor = Colors.white,
    this.trackColor = const Color(0x66FFFFFF),
  });

  @override
  State<VideoProgressSlider> createState() => _VideoProgressSliderState();
}

class _VideoProgressSliderState extends State<VideoProgressSlider> {
  double _dragValue = 0.0;
  bool _isDragging = false;
  double _thumbSize = 12.0;
  final double _activeThumbSize = 16.0;
  double _currentHeight = 4.0;

  @override
  void initState() {
    super.initState();
    _currentHeight = widget.defaultHeight;
    widget.controller.addListener(_updateProgress);
  }

  void _updateProgress() {
    if (!_isDragging && mounted) {
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.center,
      child: Container(
        height: 24.0,
        padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: LayoutBuilder(
          builder: (context, constraints) {
            return _buildProgressBar(context, constraints.maxWidth);
          },
        ),
      ),
    );
  }

  Widget _buildProgressBar(BuildContext context, double containerWidth) {
    final duration = widget.controller.value.duration;
    final position = _isDragging ? duration * _dragValue : widget.controller.value.position;
    final progress = duration.inMilliseconds > 0 ? position.inMilliseconds / duration.inMilliseconds : 0.0;

    return Container(
      color: Colors.amber,
      child: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTapDown: (details) => _handleTap(details.globalPosition, containerWidth),
        onHorizontalDragStart: (_) => _startDrag(),
        onHorizontalDragUpdate: (details) => _updateDrag(details.globalPosition, containerWidth),
        onHorizontalDragEnd: (_) => _endDrag(),
        child: Stack(
          alignment: AlignmentDirectional.centerStart,
          children: [
            // 背景轨道
            AnimatedContainer(
              duration: const Duration(milliseconds: 100),
              height: _currentHeight,
              width: containerWidth,
              decoration: BoxDecoration(
                color: widget.trackColor,
                borderRadius: BorderRadius.circular(2.0),
              ),
            ),
            // 已播放进度
            AnimatedContainer(
              duration: const Duration(milliseconds: 0),
              height: _currentHeight,
              width: progress * containerWidth,
              decoration: BoxDecoration(
                color: widget.progressColor,
                borderRadius: BorderRadius.circular(2.0),
              ),
            ),
            // 拖拽圆点
            // if (_isDragging)
            Positioned(
              left: progress * containerWidth - _thumbSize / 2,
              top: (24 - _thumbSize) / 2,
              child: Container(
                width: _thumbSize,
                height: _thumbSize,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: widget.thumbColor,
                  border: Border.all(color: widget.progressColor, width: 2.0),
                  boxShadow: [
                    BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 4.0, offset: const Offset(0, 2)),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _startDrag() {
    setState(() {
      _isDragging = true;
      _thumbSize = _activeThumbSize;
      _currentHeight = widget.defaultHeight * widget.scale;
      _dragValue = widget.controller.value.position.inMilliseconds / widget.controller.value.duration.inMilliseconds;
    });
  }

  void _endDrag() {
    widget.controller.seekTo(widget.controller.value.duration * _dragValue);
    setState(() {
      _isDragging = false;
      _thumbSize = widget.defaultThumbSize;
      _currentHeight = widget.defaultHeight;
    });
  }

  void _handleTap(Offset globalPosition, double containerWidth) {
    final box = context.findRenderObject() as RenderBox;
    final tapPos = box.globalToLocal(globalPosition);
    final newValue = (tapPos.dx / containerWidth).clamp(0.0, 1.0);
    widget.controller.seekTo(widget.controller.value.duration * newValue);
  }

  void _updateDrag(Offset globalPosition, double containerWidth) {
    final box = context.findRenderObject() as RenderBox;
    final dragPos = box.globalToLocal(globalPosition);
    final newValue = (dragPos.dx / containerWidth).clamp(0.0, 1.0);
    setState(() => _dragValue = newValue);
  }
}

调用方法

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

import 'video_progress_bar.dart';

class VideoScreen extends StatefulWidget {
  const VideoScreen({super.key});

  @override
  State<VideoScreen> createState() => _VideoScreenState();
}

class _VideoScreenState extends State<VideoScreen> {
  late VideoPlayerController _controller;

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.network(
      'https://ab27640a15b460f8185f4841a84d851.mp4',
    )..initialize().then((_) {
        setState(() {});
        _controller.play();
      });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        children: [
          Center(child: VideoPlayer(_controller)),
          Positioned(
            left: 0,
            right: 0,
            bottom: 40,
            child: VideoProgressSlider(
              controller: _controller,
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}