skip to Main Content

Like with awesome bottom sheet of Telegram i would like to have that in our application, i tired to implementing that, but unfortunately i don’t have more experience about that and i can implement below code like with that which it doesn’t have more feature.

here i attached some Gif images such as what features i want to have them on our application:

opening animation:

enter image description here

switch between tabs animation:

enter image description here

expand and collapsing animation:

enter image description here

preventing closing bottom sheet during dragging down:

enter image description here

Full code:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool isLong = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Sample'),
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
              onPressed: _onPressed,
              child: Text('open'),
            )
          ],
        ),
      ),
    );
  }

  _onPressed() {
    setImages();
    Navigator.of(context).push(TransparentRoute(builder: (context) => NewWidget(images)));
  }

  List<String> images =[];

  void setImages() {
    images = List.generate(
      isLong ? 5 : 25,
      (_) => 'http://placeimg.com/100/100/any',
    );
  }
}

class NewWidget extends StatefulWidget {
  const NewWidget(this.images, {Key? key}) : super(key: key);

  final List<String> images;

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

class _NewWidgetState extends State<NewWidget> {
  bool isBig = false;

  bool isBouncing = true;
  final double topOffset = 200;
  final double miniHandleHeight = 20;
  double safeAreaPadding = 0;
  double? startBigAnimationOffset;
  double? startStickyOffset;
  double backgroundHeight = 0;

  double get savedAppBarHeight => safeAreaPadding + kToolbarHeight;

  final ScrollController controller = ScrollController();

  @override
  void initState() {
    WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
    super.initState();
  }

  void _afterLayout(_) {
    var media = MediaQuery.of(context);
    safeAreaPadding = media.padding.top;
    startBigAnimationOffset = topOffset - savedAppBarHeight;
    startStickyOffset = (startBigAnimationOffset! + 10);
    backgroundHeight = media.size.height - miniHandleHeight - topOffset;

    controller.addListener(scrollListener);
  }

  void scrollListener() {
    var offset = controller.offset;

    if (offset < 0) {
      goOut();
    } else if (offset < startBigAnimationOffset! && isBig || offset < startStickyOffset!) {
      setState(() {
        isBig = false;
      });
    } else if (offset > startBigAnimationOffset! && !isBig || offset > startStickyOffset!) {
      setState(() {
        isBig = true;
      });
    }

    if (offset < topOffset && !isBouncing) {
      setState(() => isBouncing = true);
    } else if (offset > topOffset && isBouncing) {
      setState(() => isBouncing = false);
    }
  }

  void goOut() {
    controller.dispose();
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        backgroundColor: /*isStack ? Colors.white : */Colors.transparent,
        body: Stack(
          children: [
            ListView(
              padding: EdgeInsets.zero,
              physics: isBouncing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
              controller: controller,
              children: <Widget>[
                Container(
                  alignment: Alignment.bottomCenter,
                  height: topOffset,
                  child: TweenAnimationBuilder(
                      tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
                      duration: Duration(milliseconds: 200),
                      child: Align(
                        alignment: Alignment.topCenter,
                        child: Padding(
                          padding: EdgeInsets.only(top: 10),
                          child: Container(
                            height: 5,
                            width: 60,
                          ),
                        ),
                      ),
                      builder: (_, number, child) {
                        return Container(
                          height: savedAppBarHeight * (number as double) + miniHandleHeight,
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.vertical(
                              top: Radius.circular((1 - number) * 50),
                            ),
                            color: Colors.white,
                          ),
                          child: Opacity(opacity: 1 - (number), child: child),
                        );
                      }),
                ),
                Container(
                  padding: EdgeInsets.all(5),
                  constraints: BoxConstraints(
                    minHeight: MediaQuery.of(context).size.height - savedAppBarHeight,
                  ),
                  decoration: BoxDecoration(
                    color: Colors.white,
                  ),
                  child: getGrid(),
                )
              ],
            )
          ],
        ),
      ),
    );
  }

  Widget getGrid() {
    return GridView.count(
      crossAxisSpacing: 10,
      mainAxisSpacing: 10,
      physics: NeverScrollableScrollPhysics(),
      shrinkWrap: true,
      crossAxisCount: 3,
      children: widget.images.map((url) {
        return Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.blueAccent,
            ),
          ),
          child: Image(
            image: NetworkImage(url),
          ),
        );
      }).toList(),
    );
  }
}

class TransparentRoute extends PageRoute<void> {
  TransparentRoute({
    required this.builder,
    RouteSettings? settings,
  }) : super(settings: settings, fullscreenDialog: false);

  final WidgetBuilder builder;

  @override
  bool get opaque => false;

  @override
  Color? get barrierColor => null;

  @override
  String? get barrierLabel => null;

  @override
  bool get maintainState => true;

  @override
  Duration get transitionDuration => Duration(milliseconds: 350);

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
    final result = builder(context);
    return Container(
      color: Colors.black.withOpacity(0.5),
      child: SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(0, 1),
          end: Offset.zero,
        ).animate(CurvedAnimation(
          parent: animation,
          curve: Curves.easeIn,
        )),
        child: result,
      ),
    );
  }
}

2

Answers


  1. Here is the outline, with which you can try to implement it. If you feel difficulty, feel free to ask for more suggestions. IMHO, this is not very challenging to implement (but of course need some time to adjust the fine details into what you want).

    Firstly, you mentioned animations many times. Have a look at the official guide here: https://flutter.dev/docs/development/ui/animation. For example, if we want to implement the "opening animation" and the "switch between tabs animation" (and other similar things), we want a Container (or other widgets) to have a size changing with time as the animation goes. There are many ways to implement this, as is suggested in the link provided above. For example, we may use AnimatedContainer directly – it even has a video explaining this here. You can also use explicit animations or other things.

    Secondly, as for the animating icons, you may use Lottie https://lottiefiles.com/ to create whatever complex animation as you like. Its flutter plugin is here: https://pub.dev/packages/lottie.

    Thirdly, as for "preventing closing bottom sheet during dragging down", I will suggest the following: ListView(children: [Container(height:300,color:Colors.black), ...your_real_children...]). Then when dragging down it will not be closed, but only show a header of at most 300 height.

    Lastly, you are not restricted to use showModalBottomSheet or things like that. Indeed, when using flutter, you are completely free. So you can just do Navigator.push and draw whatever widget you like without the constraints that modal bottom sheet gives you. Personally, I suggest that you first start with showModalBottomSheet, then when it does not satisfy your needs, you just directly copy its source code and do whatever modifications as you needed. In short, use something in Flutter, and when you need more control, copy the source code and modify it.

    Login or Signup to reply.
  2. You can be helped from this link https://pub.dev/packages/modal_bottom_sheet

    import 'package:example/modals/circular_modal.dart';
    import 'package:example/web_frame.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
    
    import 'modals/floating_modal.dart';
    import 'modals/modal_complex_all.dart';
    import 'modals/modal_fit.dart';
    import 'modals/modal_inside_modal.dart';
    import 'modals/modal_will_scope.dart';
    import 'modals/modal_with_navigator.dart';
    import 'modals/modal_with_nested_scroll.dart';
    import 'modals/modal_with_scroll.dart';
    import 'modals/modal_with_page_view.dart';
    
    import 'examples/cupertino_share.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(platform: TargetPlatform.iOS),
          title: 'BottomSheet Modals',
          builder: (context, Widget? child) => WebFrame(
            child: child!,
          ),
          onGenerateRoute: (RouteSettings settings) {
            switch (settings.name) {
              case '/':
                return MaterialWithModalsPageRoute(
                    builder: (_) => MyHomePage(title: 'Flutter Demo Home Page'),
                    settings: settings);
            }
            return MaterialPageRoute(
                builder: (context) => Scaffold(
                      body: CupertinoScaffold(
                        body: Builder(
                          builder: (context) => CupertinoPageScaffold(
                            backgroundColor: Colors.white,
                            navigationBar: CupertinoNavigationBar(
                              transitionBetweenRoutes: false,
                              middle: Text('Normal Navigation Presentation'),
                              trailing: GestureDetector(
                                  child: Icon(Icons.arrow_upward),
                                  onTap: () => CupertinoScaffold
                                          .showCupertinoModalBottomSheet(
                                        expand: true,
                                        context: context,
                                        backgroundColor: Colors.transparent,
                                        builder: (context) => Stack(
                                          children: <Widget>[
                                            ModalWithScroll(),
                                            Positioned(
                                              height: 40,
                                              left: 40,
                                              right: 40,
                                              bottom: 20,
                                              child: MaterialButton(
                                                onPressed: () => Navigator.of(
                                                        context)
                                                    .popUntil((route) =>
                                                        route.settings.name == '/'),
                                                child: Text('Pop back home'),
                                              ),
                                            )
                                          ],
                                        ),
                                      )),
                            ),
                            child: Center(child: Container()),
                          ),
                        ),
                      ),
                    ),
                settings: settings);
          },
          debugShowCheckedModeBanner: false,
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Material(
          child: Scaffold(
            body: CupertinoPageScaffold(
              backgroundColor: Colors.white,
              navigationBar: CupertinoNavigationBar(
                transitionBetweenRoutes: false,
                middle: Text('iOS13 Modal Presentation'),
                trailing: GestureDetector(
                  child: Icon(Icons.arrow_forward),
                  onTap: () => Navigator.of(context).pushNamed('ss'),
                ),
              ),
              child: SizedBox.expand(
                child: SingleChildScrollView(
                  primary: true,
                  child: SafeArea(
                    bottom: false,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                        ListTile(
                            title: Text('Cupertino Photo Share Example'),
                            onTap: () => Navigator.of(context).push(
                                MaterialWithModalsPageRoute(
                                    builder: (context) => CupertinoSharePage()))),
                        section('STYLES'),
                        ListTile(
                            title: Text('Material fit'),
                            onTap: () => showMaterialModalBottomSheet(
                                  expand: false,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalFit(),
                                )),
                        ListTile(
                            title: Text('Bar Modal'),
                            onTap: () => showBarModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalInsideModal(),
                                )),
                        ListTile(
                            title: Text('Avatar Modal'),
                            onTap: () => showAvatarModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalInsideModal(),
                                )),
                        ListTile(
                            title: Text('Float Modal'),
                            onTap: () => showFloatingModalBottomSheet(
                                  context: context,
                                  builder: (context) => ModalFit(),
                                )),
                        ListTile(
                            title: Text('Cupertino Modal fit'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: false,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalFit(),
                                )),
                        section('COMPLEX CASES'),
                        ListTile(
                            title: Text('Cupertino Small Modal forced to expand'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalFit(),
                                )),
                        ListTile(
                            title: Text('Reverse list'),
                            onTap: () => showBarModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) =>
                                      ModalInsideModal(reverse: true),
                                )),
                        ListTile(
                            title: Text('Cupertino Modal inside modal'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalInsideModal(),
                                )),
                        ListTile(
                            title: Text('Cupertino Modal with inside navigation'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalWithNavigator(),
                                )),
                        ListTile(
                            title:
                                Text('Cupertino Navigator + Scroll + WillPopScope'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ComplexModal(),
                                )),
                        ListTile(
                            title: Text('Modal with WillPopScope'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  backgroundColor: Colors.transparent,
                                  builder: (context) => ModalWillScope(),
                                )),
                        ListTile(
                            title: Text('Modal with Nested Scroll'),
                            onTap: () => showCupertinoModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  builder: (context) => NestedScrollModal(),
                                )),
                        ListTile(
                            title: Text('Modal with PageView'),
                            onTap: () => showBarModalBottomSheet(
                                  expand: true,
                                  context: context,
                                  builder: (context) => ModalWithPageView(),
                                )),
                        SizedBox(
                          height: 60,
                        )
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      }
    
      Widget section(String title) {
        return Padding(
            padding: EdgeInsets.fromLTRB(16, 20, 16, 8),
            child: Text(
              title,
              style: Theme.of(context).textTheme.caption,
            ));
      }
    }
    

    another helpful link [https://pub.dev/packages/draggable_bottom_sheet][2]

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search