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

Flutter实现登录与OTP页面间背景视频连续播放方案咨询

解决方案:用Riverpod替代setState实现登录/OTP页面切换并保持背景视频连续播放

方案一:用StateProvider处理简单状态切换

如果只是单纯的页面切换需求,StateProvider足够满足需求,代码简洁且符合Riverpod规范:

  1. 定义状态Provider
    在全局或合适的位置创建StateProvider来管理当前页面状态:
// 初始状态为false,表示显示登录页面;true表示显示OTP页面
final signInPageStateProvider = StateProvider<bool>((ref) => false);
  1. 改造页面为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层分离:

  1. 定义状态类和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(),
);
  1. 在页面中使用
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 18:17:03