Flutter Web场景下基于滚动值控制视频播放进度的实现问题
Flutter Web 滚动控制视频进度实现方案
方案1:基于ScrollController实现(推荐,原生支持所有滚动手势)
你原来的思路完全可行,只需要给滚动组件增加透明占位内容即可生成滚动偏移,具体实现逻辑如下:
- 滚动组件选择
SingleChildScrollView,绑定你创建的ScrollController SingleChildScrollView的子组件使用Stack,第一层放置全屏视频组件,第二层放置透明占位SizedBox,高度设置为「屏幕高度 + 自定义总滚动行程」(总滚动行程可自行设置,比如滚动1000px对应播放完整段视频)- 监听
ScrollController的偏移变化,将偏移量按比例映射为视频进度,调用seekTo调整播放位置
核心代码示例:
import 'package:video_player/video_player.dart'; import 'package:flutter/material.dart'; class ScrollControlVideoPage extends StatefulWidget { const ScrollControlVideoPage({super.key}); @override State<ScrollControlVideoPage> createState() => _ScrollControlVideoPageState(); } class _ScrollControlVideoPageState extends State<ScrollControlVideoPage> { late ScrollController _scrollController; late VideoPlayerController _videoController; Timer? _seekTimer; // 自定义总滚动行程:滚动1000px对应播放完完整视频 final double _totalScrollDistance = 1000; @override void initState() { super.initState(); _scrollController = ScrollController(); // 初始化视频控制器,换成你自己的视频资源地址 _videoController = VideoPlayerController.asset('assets/demo_video.mp4') ..initialize().then((_) { setState(() {}); // 视频加载完成后再监听滚动事件 _scrollController.addListener(_handleScroll); }); } void _handleScroll() { if (!_videoController.value.isInitialized) return; // 滚动偏移映射为视频进度 final progress = _scrollController.offset / _totalScrollDistance; // 进度边界处理 final clampedProgress = progress.clamp(0.0, 1.0); final targetPos = _videoController.value.duration * clampedProgress; // 加防抖,避免频繁seek导致卡顿 _seekTimer?.cancel(); _seekTimer = Timer(const Duration(milliseconds: 200), () { _videoController.seekTo(targetPos); }); } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; return Scaffold( body: ScrollConfiguration( // 隐藏默认滚动条 behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), child: SingleChildScrollView( controller: _scrollController, // 强制开启滚动,不受内容高度限制 physics: const AlwaysScrollableScrollPhysics(), child: Stack( children: [ // 第一层:全屏视频 SizedBox( width: screenSize.width, height: screenSize.height, child: VideoPlayer(_videoController), ), // 第二层:透明占位,撑开滚动高度 Opacity( opacity: 0, child: SizedBox( width: screenSize.width, height: screenSize.height + _totalScrollDistance, ), ) ], ), ), ), ); } @override void dispose() { _scrollController.dispose(); _videoController.dispose(); _seekTimer?.cancel(); super.dispose(); } }
方案2:基于GestureDetector垂直拖拽实现
该方案无需引入滚动组件,更轻量,适合仅需拖拽控制的场景,需要额外适配鼠标滚轮事件:
- 用
GestureDetector包裹全屏视频组件 - 监听
onVerticalDragUpdate回调,累计垂直拖拽偏移量,按比例映射为视频进度 - 如需支持鼠标滚轮,可额外监听
PointerScrollEvent处理滚轮偏移
核心代码示例:
class DragControlVideoPage extends StatefulWidget { const DragControlVideoPage({super.key}); @override State<DragControlVideoPage> createState() => _DragControlVideoPageState(); } class _DragControlVideoPageState extends State<DragControlVideoPage> { late VideoPlayerController _videoController; Timer? _seekTimer; double _totalDragOffset = 0; // 自定义总拖拽行程:拖拽1000px对应播放完完整视频 final double _totalDragDistance = 1000; @override void initState() { super.initState(); _videoController = VideoPlayerController.asset('assets/demo_video.mp4') ..initialize().then((_) => setState(() {})); } void _updateProgress(double offsetDelta) { if (!_videoController.value.isInitialized) return; // 向上拖拽/滚动对应进度增加,所以偏移量做减法 _totalDragOffset -= offsetDelta; // 偏移边界处理 _totalDragOffset = _totalDragOffset.clamp(0, _totalDragDistance); // 映射为视频进度 final progress = _totalDragOffset / _totalDragDistance; final targetPos = _videoController.value.duration * progress; // 防抖seek _seekTimer?.cancel(); _seekTimer = Timer(const Duration(milliseconds: 200), () { _videoController.seekTo(targetPos); }); } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; return Scaffold( body: Listener( // 监听鼠标滚轮事件 onPointerSignal: (event) { if (event is PointerScrollEvent) { _updateProgress(event.scrollDelta.dy); } }, child: GestureDetector( // 监听触摸垂直拖拽事件 onVerticalDragUpdate: (details) => _updateProgress(details.delta.dy), child: SizedBox( width: screenSize.width, height: screenSize.height, child: VideoPlayer(_videoController), ), ), ), ); } @override void dispose() { _videoController.dispose(); _seekTimer?.cancel(); super.dispose(); } }
注意事项
- 两种方案都需要依赖
video_player插件,注意按照插件文档完成Web端的配置 - 防抖时长可根据实际需求调整,建议设置在100~300ms区间,平衡操作跟手性和性能
- 如果需要支持视频循环播放,可在进度超过100%时重置偏移量,实现滚动循环控制
内容的提问来源于stack exchange,提问作者Rohan Patil
相关产品推荐
相关产品推荐

