Flutter 全屏页面路由完全指南:从原理到实战

Flutter 全屏页面路由实现完全指南

在实际开发中,我们经常需要某些页面(如视频播放器、游戏、图片查看器等)以全屏模式显示。本文将详细介绍在 Flutter 中实现全屏页面的各种方法、最佳实践和常见问题解决方案。

目录

· 实现原理

· 核心方法

· 完整实现方案

· 路由集成

· 平台差异处理

· 常见问题解决

· 最佳实践

实现原理

Flutter 通过 SystemChrome 类来控制系统 UI 的显示和隐藏。全屏的本质是隐藏状态栏、导航栏等系统 UI 元素,让应用内容占据整个屏幕。

系统 UI 模式说明

模式 描述 适用场景

SystemUiMode.edgeToEdge 正常模式,显示状态栏和导航栏 普通页面

SystemUiMode.immersive 全屏模式,需要手动退出 游戏、视频播放

SystemUiMode.immersiveSticky 全屏模式,可临时显示系统 UI 图片查看、阅读器

核心方法

  1. 基本全屏控制
dart 复制代码
import 'package:flutter/services.dart';

// 进入全屏
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);

// 退出全屏
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  1. 更彻底的全屏控制
dart 复制代码
// 完全隐藏所有系统 UI
SystemChrome.setEnabledSystemUIMode(
  SystemUiMode.manual,
  overlays: [], // 空数组表示隐藏所有覆盖层
);

// 恢复正常
SystemChrome.setEnabledSystemUIMode(
  SystemUiMode.manual,
  overlays: SystemUiOverlay.values, // 显示所有覆盖层
);

完整实现方案

方案一:基于页面生命周期的实现

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:io';

class FullScreenPage extends StatefulWidget {
  final Widget child;
  final bool enableFullScreen;
  
  const FullScreenPage({
    Key? key,
    required this.child,
    this.enableFullScreen = true,
  }) : super(key: key);
  
  @override
  _FullScreenPageState createState() => _FullScreenPageState();
}

class _FullScreenPageState extends State<FullScreenPage> 
    with WidgetsBindingObserver {
    
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    
    if (widget.enableFullScreen) {
      _enterFullScreen();
    }
  }
  
  Future<void> _enterFullScreen() async {
    await SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.immersiveSticky,
    );
    
    // 可选:锁定横屏
    // await SystemChrome.setPreferredOrientations([
    //   DeviceOrientation.landscapeLeft,
    //   DeviceOrientation.landscapeRight,
    // ]);
  }
  
  Future<void> _exitFullScreen() async {
    await SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.edgeToEdge,
    );
    
    // 恢复屏幕方向
    // await SystemChrome.setPreferredOrientations([
    //   DeviceOrientation.portraitUp,
    //   DeviceOrientation.portraitDown,
    //   DeviceOrientation.landscapeLeft,
    //   DeviceOrientation.landscapeRight,
    // ]);
  }
  
  @override
  void didChangeMetrics() {
    // 当系统 UI 状态变化时,可以重新进入全屏
    if (widget.enableFullScreen) {
      _enterFullScreen();
    }
  }
  
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _exitFullScreen();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

方案二:全屏管理器类

dart 复制代码
// full_screen_manager.dart
import 'package:flutter/services.dart';
import 'dart:io';

class FullScreenManager {
  static bool _isFullScreen = false;
  
  /// 进入全屏模式
  static Future<void> enter() async {
    if (_isFullScreen) return;
    
    try {
      if (Platform.isAndroid) {
        // Android: 使用 immersiveSticky 效果最好
        await SystemChrome.setEnabledSystemUIMode(
          SystemUiMode.immersiveSticky,
        );
      } else if (Platform.isIOS) {
        // iOS: 使用 manual 模式控制更精细
        await SystemChrome.setEnabledSystemUIMode(
          SystemUiMode.manual,
          overlays: [],
        );
      }
      
      _isFullScreen = true;
      print('进入全屏模式成功');
    } catch (e) {
      print('进入全屏失败: $e');
      _isFullScreen = false;
    }
  }
  
  /// 退出全屏模式
  static Future<void> exit() async {
    if (!_isFullScreen) return;
    
    try {
      // 恢复到正常模式
      await SystemChrome.setEnabledSystemUIMode(
        SystemUiMode.edgeToEdge,
      );
      
      // 确保所有系统 UI 都显示
      await SystemChrome.setEnabledSystemUIMode(
        SystemUiMode.manual,
        overlays: SystemUiOverlay.values,
      );
      
      _isFullScreen = false;
      print('退出全屏模式成功');
    } catch (e) {
      print('退出全屏失败: $e');
    }
  }
  
  /// 切换全屏状态
  static Future<void> toggle() async {
    if (_isFullScreen) {
      await exit();
    } else {
      await enter();
    }
  }
  
  /// 获取当前状态
  static bool get isFullScreen => _isFullScreen;
  
  /// 临时显示系统 UI(仅限 immersiveSticky 模式)
  static void showSystemUI() {
    if (_isFullScreen) {
      SystemChrome.setEnabledSystemUIMode(
        SystemUiMode.manual,
        overlays: SystemUiOverlay.values,
      );
      
      // 3秒后自动隐藏
      Future.delayed(Duration(seconds: 3), () {
        if (_isFullScreen) {
          SystemChrome.setEnabledSystemUIMode(
            SystemUiMode.manual,
            overlays: [],
          );
        }
      });
    }
  }
}

方案三:全屏路由包装器

dart 复制代码
// full_screen_route.dart
import 'package:flutter/material.dart';
import 'full_screen_manager.dart';

/// 全屏路由包装器
class FullScreenRoute<T> extends MaterialPageRoute<T> {
  FullScreenRoute({
    required WidgetBuilder builder,
    RouteSettings? settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  }) : super(
          builder: builder,
          settings: settings,
          maintainState: maintainState,
          fullscreenDialog: fullscreenDialog,
        );
  
  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    // 在页面构建时进入全屏
    Future.microtask(() => FullScreenManager.enter());
    
    return FullScreenPage(
      enableFullScreen: true,
      child: builder(context),
    );
  }
  
  @override
  void didComplete(T? result) {
    // 路由完成时退出全屏
    FullScreenManager.exit();
    super.didComplete(result);
  }
}

路由集成

  1. 基本路由跳转
dart 复制代码
// 使用全屏路由跳转
Navigator.push(
  context,
  FullScreenRoute(
    builder: (context) => VideoPlayerScreen(videoUrl: videoUrl),
  ),
);

// 或者使用命名路由
MaterialApp(
  title: '全屏示例',
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/video': (context) => FullScreenPage(
      enableFullScreen: true,
      child: VideoPlayerScreen(),
    ),
  },
);
  1. 带参数的全屏路由
dart 复制代码
class FullScreenVideoPage extends StatelessWidget {
  final String videoUrl;
  
  const FullScreenVideoPage({Key? key, required this.videoUrl}) 
      : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return FullScreenPage(
      enableFullScreen: true,
      child: VideoPlayerScreen(videoUrl: videoUrl),
    );
  }
}

// 跳转
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => FullScreenVideoPage(videoUrl: 'https://example.com/video.mp4'),
  ),
);

平台差异处理

Android 特殊处理

dart 复制代码
// android_full_screen.dart
import 'package:flutter/services.dart';

class AndroidFullScreen {
  /// Android 专属的全屏处理
  static Future<void> enterFullScreen() async {
    // 方法1:推荐方式
    await SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.immersiveSticky,
    );
    
    // 方法2:更彻底的全屏
    // await SystemChrome.setEnabledSystemUIMode(
    //   SystemUiMode.immersive,
    // );
    
    // 处理返回按钮
    // SystemChrome.setPreferredOrientations([
    //   DeviceOrientation.landscapeLeft,
    //   DeviceOrientation.landscapeRight,
    // ]);
  }
  
  /// 处理 Android 物理返回键
  static Widget wrapWithBackHandler(Widget child, VoidCallback onBack) {
    return WillPopScope(
      onWillPop: () async {
        onBack();
        return false; // 阻止默认返回行为
      },
      child: child,
    );
  }
}

iOS 特殊处理

dart 复制代码
// ios_full_screen.dart
import 'package:flutter/services.dart';

class IOSFullScreen {
  /// iOS 专属的全屏处理
  static Future<void> enterFullScreen() async {
    // iOS 对全屏的支持略有不同
    await SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.manual,
      overlays: [], // 隐藏所有覆盖层
    );
    
    // iOS 通常不需要处理横屏,除非特别需求
  }
  
  /// 处理 iOS 状态栏样式
  static void setStatusBarStyle(Brightness brightness) {
    SystemChrome.setSystemUIOverlayStyle(
      SystemUiOverlayStyle(
        statusBarBrightness: brightness,
        statusBarIconBrightness: brightness == Brightness.dark 
            ? Brightness.light 
            : Brightness.dark,
      ),
    );
  }
}

常见问题解决

问题1:Android 手势导航冲突

现象:在 Android 10+ 使用手势导航时,底部导航条可能偶尔出现。

解决方案:

dart 复制代码
class FullScreenWithGestureHandler extends StatefulWidget {
  final Widget child;
  
  const FullScreenWithGestureHandler({Key? key, required this.child}) 
      : super(key: key);
  
  @override
  _FullScreenWithGestureHandlerState createState() => 
      _FullScreenWithGestureHandlerState();
}

class _FullScreenWithGestureHandlerState 
    extends State<FullScreenWithGestureHandler> {
  
  Timer? _hideTimer;
  
  @override
  void initState() {
    super.initState();
    _enterFullScreen();
  }
  
  void _enterFullScreen() async {
    // 使用更彻底的全屏模式
    await SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.immersive,
    );
  }
  
  void _onUserInteraction() {
    // 用户交互时重新隐藏系统 UI
    _hideTimer?.cancel();
    _enterFullScreen();
    
    // 设置定时器,定期检查并重新进入全屏
    _hideTimer = Timer.periodic(Duration(seconds: 1), (timer) {
      _enterFullScreen();
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _onUserInteraction,
      onPanDown: (_) => _onUserInteraction(),
      behavior: HitTestBehavior.opaque,
      child: widget.child,
    );
  }
  
  @override
  void dispose() {
    _hideTimer?.cancel();
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
    super.dispose();
  }
}

问题2:页面切换动画冲突

解决方案:自定义页面过渡

dart 复制代码
class FadeFullScreenRoute<T> extends PageRouteBuilder<T> {
  final Widget page;
  final bool enableFullScreen;
  
  FadeFullScreenRoute({
    required this.page,
    this.enableFullScreen = true,
  }) : super(
    pageBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
    ) {
      if (enableFullScreen) {
        return FullScreenPage(
          enableFullScreen: true,
          child: page,
        );
      }
      return page;
    },
    transitionsBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child,
    ) {
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
    transitionDuration: Duration(milliseconds: 300),
  );
}

问题3:多个全屏页面连续跳转

dart 复制代码
class FullScreenStackManager {
  static final List<String> _fullScreenPages = [];
  
  static void push(String pageName) {
    _fullScreenPages.add(pageName);
    if (_fullScreenPages.length == 1) {
      // 第一个全屏页面,进入全屏
      FullScreenManager.enter();
    }
  }
  
  static void pop(String pageName) {
    _fullScreenPages.remove(pageName);
    if (_fullScreenPages.isEmpty) {
      // 最后一个全屏页面退出,恢复普通模式
      FullScreenManager.exit();
    }
  }
  
  static bool isFullScreenActive() {
    return _fullScreenPages.isNotEmpty;
  }
}

// 在页面中使用
class FullScreenPageWrapper extends StatefulWidget {
  final String pageName;
  final Widget child;
  
  @override
  void initState() {
    super.initState();
    FullScreenStackManager.push(pageName);
  }
  
  @override
  void dispose() {
    FullScreenStackManager.pop(pageName);
    super.dispose();
  }
}

最佳实践

  1. 封装统一的全屏组件
dart 复制代码
// responsive_full_screen.dart
import 'package:flutter/material.dart';
import 'full_screen_manager.dart';

/// 响应式全屏组件,根据平台自动适配
class ResponsiveFullScreen extends StatefulWidget {
  final Widget child;
  final bool enableFullScreen;
  final bool enableGestureControl;
  final bool autoExitOnDispose;
  
  const ResponsiveFullScreen({
    Key? key,
    required this.child,
    this.enableFullScreen = true,
    this.enableGestureControl = true,
    this.autoExitOnDispose = true,
  }) : super(key: key);
  
  @override
  _ResponsiveFullScreenState createState() => _ResponsiveFullScreenState();
}

class _ResponsiveFullScreenState extends State<ResponsiveFullScreen> {
  @override
  void initState() {
    super.initState();
    if (widget.enableFullScreen) {
      _enterFullScreen();
    }
  }
  
  Future<void> _enterFullScreen() async {
    await FullScreenManager.enter();
  }
  
  Future<void> _exitFullScreen() async {
    if (widget.autoExitOnDispose) {
      await FullScreenManager.exit();
    }
  }
  
  void _handleUserInteraction() {
    if (widget.enableGestureControl && widget.enableFullScreen) {
      FullScreenManager.showSystemUI();
    }
  }
  
  @override
  void dispose() {
    _exitFullScreen();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return widget.enableGestureControl
        ? GestureDetector(
            onTap: _handleUserInteraction,
            behavior: HitTestBehavior.opaque,
            child: widget.child,
          )
        : widget.child;
  }
}
  1. 完整的视频播放器示例
dart 复制代码
// video_player_screen.dart
import 'package:flutter/material.dart';
import 'responsive_full_screen.dart';

class VideoPlayerScreen extends StatefulWidget {
  final String videoUrl;
  
  const VideoPlayerScreen({Key? key, required this.videoUrl}) 
      : super(key: key);
  
  @override
  _VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  bool _showControls = false;
  
  @override
  Widget build(BuildContext context) {
    return ResponsiveFullScreen(
      enableGestureControl: true,
      child: Scaffold(
        backgroundColor: Colors.black,
        body: Stack(
          children: [
            // 视频播放器
            Center(
              child: Text(
                '视频播放器',
                style: TextStyle(color: Colors.white, fontSize: 24),
              ),
            ),
            
            // 控制栏
            if (_showControls)
              Positioned(
                bottom: 0,
                left: 0,
                right: 0,
                child: Container(
                  color: Colors.black54,
                  padding: EdgeInsets.all(16),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      IconButton(
                        icon: Icon(Icons.arrow_back, color: Colors.white),
                        onPressed: () => Navigator.pop(context),
                      ),
                      IconButton(
                        icon: Icon(Icons.fullscreen_exit, color: Colors.white),
                        onPressed: () {
                          // 退出全屏逻辑
                        },
                      ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
  1. 配置示例
dart 复制代码
// main.dart
import 'package:flutter/material.dart';
import 'video_player_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 全屏示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => VideoPlayerScreen(
                      videoUrl: 'https://example.com/video.mp4',
                    ),
                  ),
                );
              },
              child: const Text('进入全屏视频播放'),
            ),
            const SizedBox(height: 20),
            const Text('提示:点击屏幕可以临时显示系统控制栏'),
          ],
        ),
      ),
    );
  }
}

总结

  1. 核心方法:使用 SystemChrome.setEnabledSystemUIMode() 控制全屏
  2. 推荐模式:SystemUiMode.immersiveSticky 用户体验最佳
  3. 生命周期管理:确保在 dispose() 中恢复系统 UI
  4. 平台适配:Android 和 iOS 需要不同的处理策略
  5. 用户体验:提供手势控制,让用户可以临时查看系统 UI
  6. 错误处理:添加适当的异常处理和状态恢复

通过以上方案,你可以灵活地在 Flutter 应用中实现全屏页面,并提供良好的用户体验。根据具体需求选择合适的实现方式,建议使用封装好的 ResponsiveFullScreen 组件来简化开发。

相关推荐
QuantumLeap丶2 小时前
《Flutter全栈开发实战指南:从零到高级》- 24 -集成推送通知
android·flutter·ios
笨小孩7873 小时前
Flutter深度解析:从入门到企业级架构实践
flutter·架构
tangweiguo030519873 小时前
Flutter 内存泄漏全面指南:检测、修复与预防
flutter
小白的程序空间3 小时前
第一章 Flutter介绍
flutter
tangweiguo030519873 小时前
Flutter发布插件:从开发到上架
flutter
嗝o゚3 小时前
Flutter 到鸿蒙开发:3个月技能迁移指南
flutter·华为·harmonyos
小a杰.3 小时前
Flutter 与其他跨平台框架的核心差异分析
flutter
嗝o゚3 小时前
Flutter + 鸿蒙实现多模态智能终端实战:语音+手势+触控融合
flutter·华为·开源