如何为Chewie视频设置高度比例?Flutter Feed适配及滚动卡顿问题
解决Flutter自定义视频Feed流组件滚动卡顿+比例适配问题
问题背景
要实现类似Instagram的Feed流,要求视频组件宽度占满屏幕,高度根据视频比例自动适配,同时具备自动播放、循环播放、静音功能。由于FlutterFlow自带的视频组件无法静音,因此基于video_player和chewie实现了自定义组件,但添加高度计算逻辑后出现滚动卡顿的问题。
原自定义组件代码
class CustomVidPlayerCopy extends StatefulWidget { CustomVidPlayerCopy({ this.width, this.height, required this.videoPath, }); final double? width; final double? height; final String videoPath; @override _CustomVidPlayerState createState() => _CustomVidPlayerState(); } class _CustomVidPlayerState extends State<CustomVidPlayerCopy> { late VideoPlayerController _videoPlayerController; late ChewieController _chewieController; bool _isVideoPlaying = false; @override void initState() { super.initState(); _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoPath)); _videoPlayerController.setVolume(0.0); _videoPlayerController.initialize().then((_) { setState(() {}); _chewieController = ChewieController( videoPlayerController: _videoPlayerController, aspectRatio: _videoPlayerController.value.aspectRatio, looping: true, showControls: false, materialProgressColors: ChewieProgressColors( playedColor: Colors.pink, handleColor: Colors.pink, ), ); }); } @override void dispose() { _videoPlayerController.dispose(); _chewieController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return VisibilityDetector( key: Key(widget.videoPath), onVisibilityChanged: (visibilityInfo) { if (visibilityInfo.visibleFraction >= 1) { _videoPlayerController.play(); } else { _videoPlayerController.pause(); } }, child: Container( child: _videoPlayerController.value.isInitialized ? Stack( children: [ Chewie( controller: _chewieController, ), GestureDetector( behavior: HitTestBehavior.opaque, ), ], ) : Center( child: CircularProgressIndicator(), ), ), ); } }
导致卡顿的新增代码
// Set default width, height, aspect ratio double get width => widget.width == null || widget.width! >= double.infinity ? MediaQuery.sizeOf(context).width : widget.width!; double get height => widget.height == null || widget.height! >= double.infinity ? width / aspectRatio : widget.height!; double get aspectRatio => _chewieController.videoPlayerController.value.aspectRatio;
解决方案
1. 卡顿原因分析
新增的getter方法会在每次组件build时重复计算尺寸,且直接依赖MediaQuery和_chewieController的实时状态,导致组件频繁重建;加上VisibilityDetector的高敏感度回调频繁触发播放/暂停操作,叠加视频渲染的性能开销,最终引发滚动卡顿。
2. 优化后的完整代码
class CustomVidPlayerCopy extends StatefulWidget { CustomVidPlayerCopy({ this.width, this.height, required this.videoPath, }); final double? width; final double? height; final String videoPath; @override _CustomVidPlayerState createState() => _CustomVidPlayerState(); } class _CustomVidPlayerState extends State<CustomVidPlayerCopy> { late VideoPlayerController _videoPlayerController; late ChewieController _chewieController; // 缓存尺寸和比例,避免重复计算 double? _cachedWidth; double? _cachedHeight; double? _cachedAspectRatio; @override void initState() { super.initState(); _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoPath)); _videoPlayerController.setVolume(0.0); _videoPlayerController.initialize().then((_) { setState(() { _cachedAspectRatio = _videoPlayerController.value.aspectRatio; _updateCachedSize(); }); _chewieController = ChewieController( videoPlayerController: _videoPlayerController, aspectRatio: _cachedAspectRatio!, looping: true, showControls: false, materialProgressColors: ChewieProgressColors( playedColor: Colors.pink, handleColor: Colors.pink, ), ); }); } @override void didChangeDependencies() { super.didChangeDependencies(); // 窗口尺寸变化时更新缓存 _updateCachedSize(); } // 单独处理尺寸计算逻辑,仅在必要时更新 void _updateCachedSize() { final targetWidth = widget.width ?? MediaQuery.sizeOf(context).width; if (_cachedWidth != targetWidth && _cachedAspectRatio != null) { setState(() { _cachedWidth = targetWidth; _cachedHeight = _cachedWidth! / _cachedAspectRatio!; }); } } @override void dispose() { _videoPlayerController.dispose(); _chewieController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return VisibilityDetector( key: Key(widget.videoPath), onVisibilityChanged: (visibilityInfo) { // 降低触发敏感度,减少播放/暂停的频繁切换 if (visibilityInfo.visibleFraction > 0.5) { if (!_videoPlayerController.value.isPlaying) { _videoPlayerController.play(); } } else if (visibilityInfo.visibleFraction < 0.2) { if (_videoPlayerController.value.isPlaying) { _videoPlayerController.pause(); } } }, child: Container( width: _cachedWidth, height: _cachedHeight, child: _videoPlayerController.value.isInitialized ? Stack( children: [ Chewie( controller: _chewieController, ), GestureDetector( behavior: HitTestBehavior.opaque, // 可选:添加点击切换播放/暂停的交互 onTap: () { if (_videoPlayerController.value.isPlaying) { _videoPlayerController.pause(); } else { _videoPlayerController.play(); } }, ), ], ) : Center(child: CircularProgressIndicator()), ), ); } }
3. 额外优化建议
- 使用
ListView.builder或SliverList构建Feed流,确保组件按需加载,避免一次性创建所有视频组件。 - 对完全移出可视区域的视频组件,可考虑临时释放控制器资源,滚动回可视区域时再重新初始化,进一步降低内存占用。
- 确保开启
video_player的硬件加速选项,提升视频渲染性能。
内容的提问来源于stack exchange,提问作者shec incrr
相关产品推荐
相关产品推荐

