16
votes

I want to design a custom navigation window like the one below.

Enter image description here

I planned to:

  1. Create my navigation widget
  2. Create my news feed widget
  3. Stack both widgets (news feed on top of the navigation)
  4. If the menu icon is clicked, translate the news feed widget to some value so that the underneath nav widget is visible

I did the first three steps. I have problems with the fourth one. I set an Offset state variable and placed my scaffold widget within a Positioned widget. I set the 'left' of the Positioned class to Offset.dx.

Code:


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

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

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
            title: 'Flutter Demo',
            theme: new ThemeData(
                primarySwatch: const MaterialColor(0xfff06000, const {
                   50: const Color(0xfffff0e6),
                  100: const Color(0xffffd1b3),
                  200: const Color(0xffffb380),
                  300: const Color(0xffff944d),
                  400: const Color(0xffff751a),
                  500: const Color(0xfff06000),
                  600: const Color(0xffcc5200),
                  700: const Color(0xffb34700),
                  800: const Color(0xff993d00),
                  900: const Color(0xff662900),
                })),
            //I stack the classes
            home: new Stack(
              children: [
                new MyNavPage(),
                new MyHomePage(title: "Home",initialOffset: new Offset(0.0, 0.0),),
              ],
            )
        );
      }
    }

    // This is my news feed class

    class MyHomePage extends StatefulWidget {

      final String title;
      final Offset initialOffset;

      MyHomePage({Key key, this.title, this.initialOffset}) : super(key: key);

      @override
      _MyHomePageState createState() => new _MyHomePageState();
    }

    class _MyHomePageState extends State with TickerProviderStateMixin {

      Offset position = new Offset(0.0, 0.0);

      int _counter = 0;

      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }

      void initState() {
        super.initState();
        position = widget.initialOffset;
      }

      @override
      Widget build(BuildContext context) {

        final scaffold = new Scaffold(
          primary: true,
          appBar: new AppBar(
            title: new Text(widget.title),
            centerTitle: true,
            leading: new IconButton(icon: new Icon(Icons.menu),onPressed: () => setState(() => position = new Offset(100.0, 0.0)),),
          ),
          backgroundColor: Colors.white30,
          body: new Container(
            child: new Center(
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  new Text(
                    'You have pushed the button this many times:',
                  ),
                  new Text(
                    '$_counter',
                    style: Theme
                        .of(context)
                        .textTheme
                        .display1,
                  ),
                ],
              ),
            ),
          ),
          floatingActionButton: new FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: new Icon(Icons.add),
          ),
        );

        return new Positioned(
          left: position.dx,
          child:scaffold,
        );
      }

    }

    // My navigation class. It has those navigation options as a column to the left.
    // The width is 100.0, hence I offset my home page by 100.0


    class MyNavPage extends StatefulWidget {
      MyNavPage({Key key}) : super(key: key);

      @override
      _MyNavPageState createState() => new _MyNavPageState();
    }

    class _MyNavPageState extends State {

      @override
      Widget build(BuildContext context) {
        Expanded createNavChild(Icon i, Text t) {
          return new Expanded(
            child: new GestureDetector(
              child: new Container(
                width: 100.0,
                decoration: new BoxDecoration(color: Colors.red,),
                child: new Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    i,
                    t
                  ],
                ),
              ),
            ),
          );
        }

        return new Scaffold(
          primary: true,
          body: new Container(
            margin: MediaQuery
                .of(context)
                .padding,
            child: new Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                createNavChild(new Icon(Icons.home, size: 30.0), new Text("Home")),
                createNavChild(
                    new Icon(Icons.person_add, size: 30.0), new Text("Register")),
                createNavChild(
                    new Icon(Icons.search, size: 30.0), new Text("Player Search")),
                createNavChild(
                    new Icon(Icons.event, size: 30.0), new Text("Events")),
                createNavChild(new Icon(Icons.file_download, size: 30.0),
                    new Text("Downloads")),
                createNavChild(
                    new Icon(Icons.call, size: 30.0), new Text("Contact")),
              ],
            ),
            decoration: new BoxDecoration(color: Colors.transparent,),
          ),
        );
      }
    }

An error is thrown:

I/flutter ( 3090): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 3090): The following assertion was thrown during performLayout():
I/flutter ( 3090): RenderCustomMultiChildLayoutBox object was given an infinite size during layout.
I/flutter ( 3090): This probably means that it is a render object that tries to be as big as possible, but it was put
I/flutter ( 3090): inside another render object that allows its children to pick their own size.
I/flutter ( 3090): The nearest ancestor providing an unbounded width constraint is:
I/flutter ( 3090):   RenderStack#df1fd NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):   creator: Stack ← Semantics ← Builder ← RepaintBoundary-[GlobalKey#274fe] ← IgnorePointer ←
I/flutter ( 3090):   FadeTransition ← FractionalTranslation ← SlideTransition ← _MountainViewPageTransition ←
I/flutter ( 3090):   AnimatedBuilder ← RepaintBoundary ← _FocusScopeMarker ← ⋯
I/flutter ( 3090):   parentData:  (can use size)
I/flutter ( 3090):   constraints: BoxConstraints(w=360.0, h=640.0)
I/flutter ( 3090):   size: Size(360.0, 640.0)
I/flutter ( 3090):   alignment: AlignmentDirectional.topStart
I/flutter ( 3090):   textDirection: ltr
I/flutter ( 3090):   fit: loose
I/flutter ( 3090):   overflow: clip
I/flutter ( 3090): The nearest ancestor providing an unbounded height constraint is:
I/flutter ( 3090):   RenderStack#df1fd NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):   creator: Stack ← Semantics ← Builder ← RepaintBoundary-[GlobalKey#274fe] ← IgnorePointer ←
I/flutter ( 3090):   FadeTransition ← FractionalTranslation ← SlideTransition ← _MountainViewPageTransition ←
I/flutter ( 3090):   AnimatedBuilder ← RepaintBoundary ← _FocusScopeMarker ← ⋯
I/flutter ( 3090):   parentData:  (can use size)
I/flutter ( 3090):   constraints: BoxConstraints(w=360.0, h=640.0)
I/flutter ( 3090):   size: Size(360.0, 640.0)
I/flutter ( 3090):   alignment: AlignmentDirectional.topStart
I/flutter ( 3090):   textDirection: ltr
I/flutter ( 3090):   fit: loose
I/flutter ( 3090):   overflow: clip
I/flutter ( 3090): The constraints that applied to the RenderCustomMultiChildLayoutBox were:
I/flutter ( 3090):   BoxConstraints(unconstrained)
I/flutter ( 3090): The exact size it was given was:
I/flutter ( 3090):   Size(Infinity, Infinity)
I/flutter ( 3090): See https://flutter.io/layout/ for more information.
I/flutter ( 3090): When the exception was thrown, this was the stack:
I/flutter ( 3090): #0      RenderBox.debugAssertDoesMeetConstraints. (package:flutter/src/rendering/box.dart:1698:9)
I/flutter ( 3090): #1      RenderBox.debugAssertDoesMeetConstraints (package:flutter/src/rendering/box.dart:1772:6)
I/flutter ( 3090): #2      RenderBox.size=. (package:flutter/src/rendering/box.dart:1507:17)
I/flutter ( 3090): #3      RenderBox.size= (package:flutter/src/rendering/box.dart:1507:65)
I/flutter ( 3090): #4      RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:354:5)
I/flutter ( 3090): #5      RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #6      _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #7      RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #8      _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #9      _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1141:11)
I/flutter ( 3090): #10     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #11     RenderStack.performLayout (package:flutter/src/rendering/stack.dart:553:15)
I/flutter ( 3090): #12     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #13     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #14     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #15     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #16     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #17     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #18     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #19     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #20     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #21     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #22     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #23     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #24     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #25     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #26     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #27     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #28     RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:2712:13)
I/flutter ( 3090): #29     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #30     RenderStack.performLayout (package:flutter/src/rendering/stack.dart:514:15)
I/flutter ( 3090): #31     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #32     __RenderTheatre&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #33     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #34     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #35     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #36     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #37     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #38     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #39     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #40     _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:107:13)
I/flutter ( 3090): #41     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
I/flutter ( 3090): #42     RenderView.performLayout (package:flutter/src/rendering/view.dart:125:13)
I/flutter ( 3090): #43     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1445:7)
I/flutter ( 3090): #44     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:709:18)
I/flutter ( 3090): #45     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:270:19)
I/flutter ( 3090): #46     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:627:13)
I/flutter ( 3090): #47     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208:5)
I/flutter ( 3090): #48     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
I/flutter ( 3090): #49     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
I/flutter ( 3090): #50     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.scheduleWarmUpFrame. (package:flutter/src/scheduler/binding.dart:751:7)
I/flutter ( 3090): #52     _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:382:19)
I/flutter ( 3090): #53     _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5)
I/flutter ( 3090): #54     _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)
I/flutter ( 3090): (elided one frame from package dart:async)
I/flutter ( 3090): The following RenderObject was being processed when the exception was fired:
I/flutter ( 3090):   RenderCustomMultiChildLayoutBox#04aef relayoutBoundary=up3 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):   creator: CustomMultiChildLayout ← AnimatedBuilder ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
I/flutter ( 3090):   _InkFeatures-[GlobalKey#64807 ink renderer] ← NotificationListener ←
I/flutter ( 3090):   PhysicalModel ← AnimatedPhysicalModel ← Material ← PrimaryScrollController ← _ScaffoldScope ←
I/flutter ( 3090):   Scaffold ← ⋯
I/flutter ( 3090):   parentData:  (can use size)
I/flutter ( 3090):   constraints: BoxConstraints(unconstrained)
I/flutter ( 3090):   size: Size(Infinity, Infinity)
I/flutter ( 3090): This RenderObject had the following descendants (showing up to depth 5):
I/flutter ( 3090):   RenderPositionedBox#4ac32 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):     RenderFlex#a08f4 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):       RenderParagraph#eba89 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):       RenderParagraph#5afd6 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):   RenderConstrainedBox#0b71f NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):     RenderPhysicalModel#fa853 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):       _RenderInkFeatures#45d75 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):         RenderPositionedBox#7bd87 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):           RenderPadding#3faff NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):   RenderStack#4eccb NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):     RenderTransform#16934 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):       RenderTransform#317f7 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):         RenderSemanticsAnnotations#f02cf NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090):           RenderConstrainedBox#75c14 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 3090): ════════════════════════════════════════════════════════════════════════════════════════════════════

Questions:

  1. Is my approach correct?
  2. If it is correct, what is the error telling me?
  3. If it is not the right approach, is there a simpler or better way to achieve this?
4
Why is there a new MyNavPage() at the start of the build method? Have you tried using/extending a standard app drawer with Scaffold and changing the width?Jacob Phillips
@JacobPhillips My bad. That line shouldn't be there. Edited it. As to your second question, I don't want the drawer to slide on top of my home widget. I want the home widget itself to slide and reveal the drawer beneath it. That is why I didn't use the inbuilt drawer.Nirup Iyer

4 Answers

5
votes

Let me know if I'm wrong about this, but it sounds like you want the navigation drawer to open when the user clicks the menu button. Thankfully, Flutter already handles this!

You can simply use the Scaffold's drawer property. You pass it a drawer (or possibly another widget) to show, and it will automatically handle making it available for swiping in from the left.

If you also want to open it on a button press, you can use Scaffold.of(context).openDrawer(); from your button. Note that to get the context that includes the scaffold, you'll have to use a Builder or make your appbar a new widget.

3
votes

You can achieve it by using this: flutter_inner_drawer

Add this to your package's pubspec.yaml file:

dependencies:
  flutter_inner_drawer: "^0.5.7+2"

Simple usage

import 'package:flutter_inner_drawer/inner_drawer.dart';
.
.
.
   
    @override
    Widget build(BuildContext context)
    {
        return InnerDrawer(
            key: _innerDrawerKey,
            onTapClose: true, // default false
            swipe: true, // default true            
            colorTransitionChild: Color.red, // default Color.black54
            colorTransitionScaffold: Color.black54, // default Color.black54
            
            //When setting the vertical offset, be sure to use only top or bottom
            offset: IDOffset.only(bottom: 0.05, right: 0.0, left: 0.0),
            
            scale: IDOffset.horizontal( 0.8 ), // set the offset in both directions
            
            proportionalChildArea : true, // default true
            borderRadius: 50, // default 0
            leftAnimationType: InnerDrawerAnimation.static, // default static
            rightAnimationType: InnerDrawerAnimation.quadratic,
            backgroundDecoration: BoxDecoration(color: Colors.red ), // default  Theme.of(context).backgroundColor
            
            //when a pointer that is in contact with the screen and moves to the right or left            
            onDragUpdate: (double val, InnerDrawerDirection direction) {
                // return values between 1 and 0
                print(val);
                // check if the swipe is to the right or to the left
                print(direction==InnerDrawerDirection.start);
            },
            
            innerDrawerCallback: (a) => print(a), // return  true (open) or false (close)
            leftChild: Container(), // required if rightChild is not set
            rightChild: Container(), // required if leftChild is not set
            
            //  A Scaffold is generally used but you are free to use other widgets
            // Note: use "automaticallyImplyLeading: false" if you do not personalize "leading" of Bar
            scaffold: Scaffold(
                appBar: AppBar(
                    automaticallyImplyLeading: false
                ),
            ) 
            /* OR
            CupertinoPageScaffold(                
                navigationBar: CupertinoNavigationBar(
                    automaticallyImplyLeading: false
                ),
            ), 
            */
        )
    }
    
    //  Current State of InnerDrawerState
    final GlobalKey<InnerDrawerState> _innerDrawerKey = GlobalKey<InnerDrawerState>();    

    void _toggle()
    {
       _innerDrawerKey.currentState.toggle(
       // direction is optional 
       // if not set, the last direction will be used
       //InnerDrawerDirection.start OR InnerDrawerDirection.end                        
        direction: InnerDrawerDirection.end 
       );
    }

enter image description here

0
votes

So after some research, I found this wonderful video on YouTube. It is very informative and exactly solves my problem.

He has used the same approach, but with much better code. I would recommend anyone who is learning Flutter to watch all his videos.

Link to video here.

0
votes

Packages Hidden Drawer Menu

Image

These packages are very simple to use. Link to the packages is here.