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

如何在Flutter ListView中实现类似YouTube的滚动时顶部视频自动播放

Flutter ListView 实现顶部视频自动播放(仿YouTube主界面)

要实现这个功能,核心是监听滚动、判断可视区域内的顶部视频、切换播放状态,下面是具体的实现方案:

核心步骤

  • 依赖video_player插件处理视频播放逻辑
  • 用ScrollController监听ListView的滚动事件
  • 计算每个列表项的位置,确定当前处于视口顶部的视频
  • 维护全局的视频控制器实例,切换时暂停旧视频、播放新视频

具体实现代码

1. 添加依赖

在pubspec.yaml中加入:

dependencies:
  flutter:
    sdk: flutter
  video_player: ^2.8.1

2. 实现列表与滚动监听

class VideoListScreen extends StatefulWidget {
  @override
  _VideoListScreenState createState() => _VideoListScreenState();
}

class _VideoListScreenState extends State<VideoListScreen> {
  final ScrollController _scrollController = ScrollController();
  VideoPlayerController? _currentPlayingController;
  final List<String> _videoUrls = [
    "https://example.com/video1.mp4",
    "https://example.com/video2.mp4",
    // 补充更多视频链接
  ];
  final List<GlobalKey> _itemKeys = [];

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
    // 为每个列表项生成唯一key
    for (int i = 0; i < _videoUrls.length; i++) {
      _itemKeys.add(GlobalKey());
    }
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _currentPlayingController?.dispose();
    super.dispose();
  }

  void _onScroll() {
    // 滚动节流,避免频繁触发检测
    Future.delayed(const Duration(milliseconds: 100), () {
      _checkVisibleVideos();
    });
  }

  Future<void> _checkVisibleVideos() async {
    final RenderBox listViewBox = context.findRenderObject() as RenderBox;
    final viewportHeight = listViewBox.size.height;

    // 遍历列表项,找到处于视口顶部的视频
    for (int i = 0; i < _itemKeys.length; i++) {
      final renderBox = _itemKeys[i].currentContext?.findRenderObject() as RenderBox?;
      if (renderBox == null) continue;

      final itemRect = renderBox.localToGlobal(Offset.zero, ancestor: listViewBox);
      // 判断视频是否进入视口上半区域(可根据需求调整阈值)
      if (itemRect.top >= 0 && itemRect.top <= viewportHeight / 2) {
        await _playVideo(i);
        break;
      }
    }
  }

  Future<void> _playVideo(int index) async {
    final url = _videoUrls[index];
    // 若当前播放的就是目标视频,直接返回
    if (_currentPlayingController?.dataSource == url) return;

    final controller = VideoPlayerController.networkUrl(Uri.parse(url));
    await controller.initialize();

    // 暂停并销毁旧控制器
    _currentPlayingController?.pause();
    _currentPlayingController?.dispose();

    // 播放新视频
    setState(() {
      _currentPlayingController = controller;
    });
    await _currentPlayingController?.play();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        controller: _scrollController,
        itemCount: _videoUrls.length,
        itemBuilder: (context, index) {
          return _buildVideoItem(index);
        },
      ),
    );
  }

  Widget _buildVideoItem(int index) {
    final isPlaying = _currentPlayingController?.dataSource == _videoUrls[index];
    return Container(
      key: _itemKeys[index],
      height: 200,
      margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
      child: isPlaying
          ? VideoPlayer(_currentPlayingController!)
          : Image.network(
              "https://example.com/thumbnail$index.jpg",
              fit: BoxFit.cover,
            ), // 缩略图占位
    );
  }
}

关键优化点

  • 控制器复用:只维护当前播放的视频控制器,减少不必要的资源消耗
  • 滚动节流:通过延迟执行检测逻辑,避免滚动时频繁触发计算
  • 生命周期适配:可通过WidgetsBindingObserver监听App前后台状态,后台时暂停视频、前台恢复
  • 可视区域判断:若觉得手动计算位置麻烦,可使用visibility_detector插件简化可视区域检测逻辑

内容的提问来源于stack exchange,提问作者Ameer Safdar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 01:45:45