Search code examples
animationdartflutterbottom-sheet

Implementing transitions in a BottomSheet


I'm trying to implement the following design, but I can't seem to get my head around the way I should do it :P

gif

I was thinking about using a BottomSheet displayed via the showModalBottomSheet function, but I can't figure out how to implement the transitions (I'd use a FadeTransition for the fade effect, no idea for the the height-changing effect though)

What I got so far :

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

class Setup extends StatefulWidget {
  final Widget child;

  const Setup(this.child);

  @override
  _SetupState createState() => _SetupState();
}

class MyCurve extends Curve {
  @override
  double transform(double t) => -pow(t, 2) + 1;
}

class _SetupState extends State<Setup> with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> opacityAnimation;
  int i = 0;

  @override
  void initState() {
    super.initState();

    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 500));
    opacityAnimation = CurvedAnimation(
        parent: Tween<double>(begin: 1, end: 0).animate(_controller),
        curve: Curves.easeInOutExpo);
  }

  @override
  Widget build(BuildContext context) {
    return BottomSheet(
      enableDrag: false,
      elevation: 16,
      backgroundColor: Colors.transparent,
      builder: (_) => Container(
            margin: EdgeInsets.all(8),
            decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)),
            child: Material(
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(16)),
                child: FadeTransition(
                  opacity: opacityAnimation,
                  child: Padding(
                      padding: EdgeInsets.all(16),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          widget.child,
                          Container(
                            height: 10,
                          ),
                          RaisedButton(
                            child: Text("Next"),
                            onPressed: () {
                              _controller.forward().then((_) {
                                _controller.reverse();
                              });
                            },
                          )
                        ],
                      )),
                )),
          ),
      onClosing: () {},
    );
  }
}

As you can see, I just got the fade animation working, and got none of the routing or height transition done.


Solution

  • Why complicate things when you can achieve the same using AnimatedContainer and AnimateCrossFade.

    enter image description here

    Just for your information

        class BS extends StatefulWidget {
          _BS createState() => _BS();
        }
    
        class _BS extends State<BS> {
          bool _showSecond = false;
    
          @override
          Widget build(BuildContext context) {
            return BottomSheet(
              onClosing: () {},
              builder: (BuildContext context) => AnimatedContainer(
                margin: EdgeInsets.all(20),
                decoration: BoxDecoration(
                    color: Colors.white, borderRadius: BorderRadius.circular(30)),
                child: AnimatedCrossFade(
                    firstChild: Container(
                      constraints: BoxConstraints.expand(
                          height: MediaQuery.of(context).size.height - 200),
    //remove constraint and add your widget hierarchy as a child for first view
                      padding: EdgeInsets.all(20),
                      child: Align(
                        alignment: Alignment.bottomCenter,
                        child: RaisedButton(
                          onPressed: () => setState(() => _showSecond = true),
                          padding: EdgeInsets.all(15),
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(10)),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              Text("Suivant"),
                            ],
                          ),
                        ),
                      ),
                    ),
                    secondChild: Container(
                      constraints: BoxConstraints.expand(
                          height: MediaQuery.of(context).size.height / 3),
    //remove constraint and add your widget hierarchy as a child for second view
                      padding: EdgeInsets.all(20),
                      child: Align(
                        alignment: Alignment.bottomCenter,
                        child: RaisedButton(
                          onPressed: () => setState(() => _showSecond = false),
                          color: Colors.green,
                          padding: EdgeInsets.all(15),
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(10)),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              Text("ok"),
                            ],
                          ),
                        ),
                      ),
                    ),
                    crossFadeState: _showSecond
                        ? CrossFadeState.showSecond
                        : CrossFadeState.showFirst,
                    duration: Duration(milliseconds: 400)),
                duration: Duration(milliseconds: 400),
              ),
            );
          }
        }