Flutter实现登录与OTP页面间背景视频连续播放方案咨询
解决方案:用Riverpod替代setState实现登录/OTP页面切换并保持背景视频连续播放
方案一:用StateProvider处理简单状态切换
如果只是单纯的页面切换需求,StateProvider足够满足需求,代码简洁且符合Riverpod规范:
- 定义状态Provider
在全局或合适的位置创建StateProvider来管理当前页面状态:
// 初始状态为false,表示显示登录页面;true表示显示OTP页面 final signInPageStateProvider = StateProvider<bool>((ref) => false);
- 改造页面为ConsumerStatefulWidget
因为需要管理文本控制器并正确释放,使用ConsumerStatefulWidget替代原有的StatefulWidget:
class SignIn extends ConsumerStatefulWidget { const SignIn({super.key}); @override ConsumerState<SignIn> createState() => _SignInState(); } class _SignInState extends ConsumerState<SignIn> { final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _pinController = TextEditingController(); @override void dispose() { _emailController.dispose(); _passwordController.dispose(); _pinController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // 监听状态变化 final isOtp = ref.watch(signInPageStateProvider); final otpContent = Column( children: [ Gap(10.h), Text( "输入OTP", style: TextStyles.title, ), Gap(20.h), Pinput( controller: _pinController, length: 4, ), Gap(20.h), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF005B), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), onPressed: () { // 验证OTP后可切换回登录页 ref.read(signInPageStateProvider.notifier).state = false; }, child: Text( "验证OTP", style: TextStyles.subTitle, ), ), ], ); final loginContent = Column( children: [ Gap(10.h), AuthInput( controller: _emailController, hintText: '邮箱/用户名', keyboardType: TextInputType.emailAddress, ), Gap(20.h), AuthInput( controller: _passwordController, hintText: '密码', inputVisible: false, ), Gap(20.h), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF005B), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), onPressed: () { // 登录逻辑完成后切换到OTP页面 ref.read(signInPageStateProvider.notifier).state = true; }, child: Text( "开始", style: TextStyles.subTitle, ), ), ], ); return VideoBackground( mainContent: SignInCommon(mainLoginContent: isOtp ? otpContent : loginContent), ); } }
方案二:用StateNotifier处理复杂业务逻辑
如果后续需要添加登录请求、OTP验证等异步操作,推荐使用StateNotifier将业务逻辑与UI层分离:
- 定义状态类和Notifier
// 封装页面状态,可扩展加载状态、错误信息等字段 class SignInState { final bool isOtp; SignInState({required this.isOtp}); } class SignInNotifier extends StateNotifier<SignInState> { SignInNotifier() : super(SignInState(isOtp: false)); // 切换到OTP页面 void switchToOtp() { state = SignInState(isOtp: true); } // 切换回登录页面 void switchToLogin() { state = SignInState(isOtp: false); } // 模拟登录请求 Future<void> login(String email, String password) async { // 这里替换为实际登录接口调用 await Future.delayed(const Duration(seconds: 1)); switchToOtp(); } // 模拟OTP验证 Future<void> verifyOtp(String otp) async { // 这里替换为实际OTP验证接口调用 await Future.delayed(const Duration(seconds: 1)); switchToLogin(); } } // 定义对应的Provider final signInNotifierProvider = StateNotifierProvider<SignInNotifier, SignInState>( (ref) => SignInNotifier(), );
- 在页面中使用
class _SignInState extends ConsumerState<SignIn> { final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _pinController = TextEditingController(); @override void dispose() { _emailController.dispose(); _passwordController.dispose(); _pinController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final signInState = ref.watch(signInNotifierProvider); final signInNotifier = ref.read(signInNotifierProvider.notifier); final otpContent = Column( children: [ Gap(10.h), Text( "输入OTP", style: TextStyles.title, ), Gap(20.h), Pinput( controller: _pinController, length: 4, ), Gap(20.h), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF005B), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), onPressed: () async { await signInNotifier.verifyOtp(_pinController.text); }, child: Text( "验证OTP", style: TextStyles.subTitle, ), ), ], ); final loginContent = Column( children: [ Gap(10.h), AuthInput( controller: _emailController, hintText: '邮箱/用户名', keyboardType: TextInputType.emailAddress, ), Gap(20.h), AuthInput( controller: _passwordController, hintText: '密码', inputVisible: false, ), Gap(20.h), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF005B), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), onPressed: () async { await signInNotifier.login(_emailController.text, _passwordController.text); }, child: Text( "开始", style: TextStyles.subTitle, ), ), ], ); return VideoBackground( mainContent: SignInCommon(mainLoginContent: signInState.isOtp ? otpContent : loginContent), ); } }
核心优势
- 两种方案都完全替代了
setState,符合Riverpod的状态管理最佳实践 VideoBackground组件不依赖状态变化,因此不会被重建,背景视频可以保持连续播放- 状态逻辑集中管理,便于后续扩展和维护
内容的提问来源于stack exchange,提问作者Lokesh Mishra
相关产品推荐
相关产品推荐

