You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:25:27