HTMHell
HTMHell

Reputation: 6006

Sliding form steps in Flutter?

I'm creating a registration form in Flutter, and I would like the user to go through steps. Every step should transition to the next step with a sliding effect. For example, if I am on Step 1, moving to Step 2 should slide the form to the left, and I should get Form 2. Then if I go back to form 1, it should slide the form to the right.

Here's an illustration: enter image description here

I tried to do that with multiple routes:

routes: {
    '/': (context) => HomePage(),
    '/step1': (context) => FormStep1(),
    '/step2': (context) => FormStep2(),
},

Then on submit:

Navigator.push(
    context,
    EnterExitRoute(exitPage: FormStep1(), enterPage: FormStep2())
);

EnterExitRoute

But that makes the App Bar slide as well, and I want only the form to slide.

Upvotes: 8

Views: 9838

Answers (2)

HTMHell
HTMHell

Reputation: 6006

With an advice from a friend, I ended up using PageView. That way I didn't have to make a new route for every step.

class _RegisterFormState extends State<RegisterForm> {
  final _formsPageViewController = PageController();
  List _forms;


  @override
  Widget build(BuildContext context) {
    _forms = [
      WillPopScope(
        onWillPop: () => Future.sync(this.onWillPop),
        child: Step1Container(),
      ),
      WillPopScope(
        onWillPop: () => Future.sync(this.onWillPop),
        child: Step2Container(),
      ),
    ];

    return Expanded(
      child: PageView.builder(
        controller: _formsPageViewController,
        physics: NeverScrollableScrollPhysics(),
        itemBuilder: (BuildContext context, int index) {
          return _forms[index];
        },
      ),
    );
  }

  void _nextFormStep() {
    _formsPageViewController.nextPage(
      duration: Duration(milliseconds: 300),
      curve: Curves.ease,
    );
  }

  bool onWillPop() {
    if (_formsPageViewController.page.round() ==
        _formsPageViewController.initialPage) return true;

    _formsPageViewController.previousPage(
      duration: Duration(milliseconds: 300),
      curve: Curves.ease,
    );

    return false;
  }
}

Explanation:

  • I'm wrapping every form with WillPopScope so "back" button will affect navigation.
  • I'm using physics: NeverScrollableScrollPhysics() option on the PageView builder so it will not be affected by a swipe gesture.
  • On each button of a form step (except last step) I call the _nextFormStep() method, which moves to the next form.
  • The child of each WillPopScope() in the list is simply the form / widget you want to be slided.

Upvotes: 13

user10539074
user10539074

Reputation:

as an option you can wrap pages with Navigator widget something like this

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Title')),
        body: SafeArea(
          child: WillPopScope(
            onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
            child: Navigator(
              key: _navigatorKey,
              onGenerateRoute: (settings) {
                switch (settings.name) {
                  case '/':
                    return MaterialPageRoute(builder: (context) => HomePage());
                    break;
                  case '/step1':
                    return CupertinoPageRoute(builder: (context) => FormStep1());
                    break;
                  case '/step2':
                    return CupertinoPageRoute(builder: (context) => FormStep2());
                    break;
                }
              },
            ),
          ),
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green[200],
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('HomePage'),
          RaisedButton(
            onPressed: () => Navigator.pushNamed(context, '/step1'),
            child: Text('Start'),
          ),
        ],
      ),
    );
  }
}

class FormStep1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue[200],
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('FormStep1'),
          RaisedButton(
            onPressed: () => Navigator.pushNamed(context, '/step2'),
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

class FormStep2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow[200],
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('FormStep2'),
          RaisedButton(onPressed: () {}, child: Text('Next')),
        ],
      ),
    );
  }
}

enter image description here

also instead of CupertinoPageRoute you can use any custom Route with any transition

Upvotes: 1

Related Questions