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

Flutter中Youtube播放器全屏时底部溢出问题求助

问题解决:Flutter YoutubePlayer全屏切换时底部Widget溢出

核心问题分析

  1. 控制器初始化位置错误:你在build方法里每次重建都会重新创建YoutubePlayerController,导致播放器状态不稳定,全屏切换时布局异常。
  2. 布局适配不足:横屏全屏时屏幕高度大幅缩小,原布局的Column + Flexible无法适配高度变化,导致底部内容溢出。
  3. SingleChildScrollView嵌套错误:之前直接包裹整个body导致内容不显示,正确的做法是只包裹底部的可变内容区域。

具体修复步骤

1. 修正控制器初始化逻辑

把控制器初始化移到didChangeDependencies(而非build),保证只在视频ID变化时重建,同时添加状态监听:

late YoutubePlayerController _controller;
String? _videoID;

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  final productId = ModalRoute.of(context)!.settings.arguments as String;
  final productProvider = Provider.of<ProductsProvider>(context);
  final getCurrProduct = productProvider.findProdById(productId);
  final videoURL = getCurrProduct.youtube;
  final videoID = YoutubePlayer.convertUrlToId(videoURL);
  
  // 仅当视频ID变化时重建控制器
  if (_videoID != videoID) {
    _videoID = videoID;
    _controller = YoutubePlayerController(
      initialVideoId: videoID!,
      flags: const YoutubePlayerFlags(autoPlay: false),
    )..addListener(() {
        setState(() {}); // 监听状态变化,触发布局更新
      });
  }
}

@override
void dispose() {
  _quantityTextController.dispose();
  _controller.dispose(); // 新增:释放控制器资源
  super.dispose();
}

2. 适配全屏状态的布局调整

根据播放器的全屏状态动态切换布局,非全屏时用SingleChildScrollView包裹底部内容避免溢出:

@override
Widget build(BuildContext context) {
  Size size = Utils(context).getScreenSize;
  final Color color = Utils(context).color;
  final cartProvider = Provider.of<CartProvider>(context);
  final productId = ModalRoute.of(context)!.settings.arguments as String;
  final productProvider = Provider.of<ProductsProvider>(context);
  final getCurrProduct = productProvider.findProdById(productId);
  double usedPrice = getCurrProduct.isOnSale
      ? getCurrProduct.salePrice
      : getCurrProduct.price;
  double totalPrice = usedPrice * int.parse(_quantityTextController.text);
  bool? _isInCart = cartProvider.getCartItems.containsKey(getCurrProduct.id);
  final wishlistProvider = Provider.of<WishlistProvider>(context);
  bool? _isInWishlist =
      wishlistProvider.getWishlistItems.containsKey(getCurrProduct.id);
  final viewedProdProvider = Provider.of<ViewedProdProvider>(context);
  
  bool _isFullScreen = _controller.value.isFullScreen;
  
  return WillPopScope(
    onWillPop: () async {
      // 全屏状态下先退出全屏再返回
      if (_isFullScreen) {
        _controller.toggleFullScreenMode();
        return false;
      }
      await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
      return true;
    },
    child: Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: _isFullScreen ? null : AppBar(
          leading: InkWell(
            borderRadius: BorderRadius.circular(12),
            onTap: () {
              SystemChrome.setPreferredOrientations([
                DeviceOrientation.portraitUp,
              ]);
              Navigator.canPop(context) ? Navigator.pop(context) : null;
            },
            child: Icon(
              IconlyLight.arrowLeft2,
              color: color,
              size: 24,
            ),
          ),
          elevation: 0,
          backgroundColor: Theme.of(context).scaffoldBackgroundColor),
      body: _isFullScreen
          ? YoutubePlayer(
              controller: _controller,
              showVideoProgressIndicator: true,
              onReady: () => debugPrint('Ready'),
              bottomActions: [
                CurrentPosition(),
                ProgressBar(
                  isExpanded: true,
                  colors: const ProgressBarColors(
                      playedColor: Colors.green, handleColor: Colors.greenAccent),
                ),
                const PlaybackSpeedButton(),
                FullScreenButton(),
              ],
            )
          : Column(
              children: [
                YoutubePlayer(
                  controller: _controller,
                  showVideoProgressIndicator: true,
                  onReady: () => debugPrint('Ready'),
                  bottomActions: [
                    CurrentPosition(),
                    ProgressBar(
                      isExpanded: true,
                      colors: const ProgressBarColors(
                          playedColor: Colors.green, handleColor: Colors.greenAccent),
                    ),
                    const PlaybackSpeedButton(),
                    FullScreenButton(),
                  ],
                ),
                Expanded(
                  child: SingleChildScrollView(
                    physics: const BouncingScrollPhysics(),
                    child: Container(
                      decoration: BoxDecoration(
                        color: Theme.of(context).cardColor,
                        borderRadius: const BorderRadius.only(
                          topLeft: Radius.circular(40),
                          topRight: Radius.circular(40),
                        ),
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Padding(
                            padding: const EdgeInsets.only(
                                top: 20, left: 30, right: 30, bottom: 0),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                Flexible(
                                  child: TextWidget(
                                    text: getCurrProduct.title,
                                    color: color,
                                    textSize: 25,
                                    isTitle: true,
                                  ),
                                ),
                              ],
                            ),
                          ),
                          Padding(
                            padding:
                                const EdgeInsets.only(top: 20, left: 30, right: 30),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.start,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                SizedBox(
                                  width: 10,
                                ),
                                const Spacer(),
                              ],
                            ),
                          ),
                          const SizedBox(
                            height: 5,
                          ),
                          Container(
                            padding: const EdgeInsets.all(16.0),
                            child: TextWidget(
                              text: getCurrProduct.description,
                              color: Colors.black,
                              textSize: 18,
                              isTitle: true,
                              maxLines: 4,
                            ),
                          ),
                          const SizedBox(
                            height: 50,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
    ),
  );
}

3. 其他细节优化

  • 全屏时隐藏AppBar,避免播放器被遮挡;
  • 返回键逻辑优化:全屏状态下先退出全屏,再执行页面返回;
  • 用Expanded代替Flexible,确保底部内容占据剩余空间,同时SingleChildScrollView允许内容滚动,彻底解决溢出问题。

内容的提问来源于stack exchange,提问作者Raphael Elimimian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:53:11