Flutter (36): Scaffold

Time: Column:Mobile & Frontend views:227

The Material component library offers a variety of rich components. This section introduces the most commonly used Scaffold component. For the others, readers can refer to the documentation or explore the examples in the Material section of the Flutter Gallery.

Note: Flutter Gallery is the official demo app provided by Flutter. Its source code is located in the examples directory of the Flutter source code. I strongly recommend users run the Flutter Gallery examples, as it's a comprehensive showcase of Flutter and an excellent reference for learning. It was my primary resource when learning Flutter.

36.1 Scaffold

A complete route page may include a navigation bar, a drawer menu, and a bottom tab navigation bar. Manually implementing these elements on every route page would be both cumbersome and tedious. Fortunately, the Flutter Material component library provides ready-to-use components that can reduce our development workload. Scaffold acts as the skeleton of a route page, allowing us to easily assemble a complete page.

Example

Let’s implement a page that includes:

  • A navigation bar

  • A share button on the right side of the navigation bar

  • A drawer menu

  • A bottom navigation bar

  • A floating action button in the lower right corner

The final effect is shown in figures.

Flutter (36): Scaffold

Code implementation:

class ScaffoldRoute extends StatefulWidget {
  @override
  _ScaffoldRouteState createState() => _ScaffoldRouteState();
}

class _ScaffoldRouteState extends State<ScaffoldRoute> {
  int _selectedIndex = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( // Navigation bar
        title: Text("App Name"),
        actions: <Widget>[ // Right-side menu on the navigation bar
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
      ),
      drawer: MyDrawer(), // Drawer
      bottomNavigationBar: BottomNavigationBar( // Bottom navigation
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
          BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
          BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.blue,
        onTap: _onItemTapped,
      ),
      floatingActionButton: FloatingActionButton( // Floating action button
          child: Icon(Icons.add),
          onPressed:_onAdd
      ),
    );
  }

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  void _onAdd() {}
}

In the above code, we used the following components:

Component NameDescription
AppBarA framework for the navigation bar
MyDrawerDrawer menu
BottomNavigationBarBottom navigation bar
FloatingActionButtonFloating action button

Let’s now take a closer look at each of them.


36.2 AppBar

AppBar is a Material-style navigation bar that allows setting the page title, navigation menu, and bottom tab titles. Here's the definition of AppBar:

AppBar({
  Key? key,
  this.leading, // The left-most widget in the navigation bar, typically a drawer menu button or a back button.
  this.automaticallyImplyLeading = true, // Automatically implement a default leading button if leading is null.
  this.title, // Page title
  this.actions, // Right-side menu on the navigation bar
  this.bottom, // Bottom menu of the navigation bar, usually a Tab button group
  this.elevation = 4.0, // Shadow of the navigation bar
  this.centerTitle, // Whether the title is centered
  this.backgroundColor,
  ...   // Other attributes can be found in the source code comments
})

If a drawer menu is added to the Scaffold, by default, Scaffold will automatically set the leading of the AppBar to be a menu button . Clicking it will open the drawer. If we want to customize the menu icon, we can manually set the leading, like this:

Scaffold(
  appBar: AppBar(
    title: Text("App Name"),
    leading: Builder(builder: (context) {
      return IconButton(
        icon: Icon(Icons.dashboard, color: Colors.white), // Custom icon
        onPressed: () {
          // Open the drawer menu
          Scaffold.of(context).openDrawer(); 
        },
      );
    }),
    ...  
  )  
)

The running effect of the code is shown in figure .

Flutter (36): Scaffold

As we can see, the left-side menu has been successfully replaced.

The method to open the drawer menu comes from the ScaffoldState. You can obtain the closest Scaffold component’s state object using Scaffold.of(context).


36.3 Drawer

The drawer and endDrawer properties of Scaffold can accept a Widget as the left or right drawer menu of the page, respectively. If the developer provides a drawer menu, the user can swipe in from the left (or right) side of the screen to open it. At the beginning of this section, we implemented a left drawer menu, MyDrawer. The source code is as follows:

class MyDrawer extends StatelessWidget {
  const MyDrawer({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: MediaQuery.removePadding(
        context: context,
        // Remove the default top padding from the drawer menu
        removeTop: true,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(top: 38.0),
              child: Row(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: ClipOval(
                      child: Image.asset(
                        "imgs/avatar.png",
                        width: 80,
                      ),
                    ),
                  ),
                  Text(
                    "Wendux",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )
                ],
              ),
            ),
            Expanded(
              child: ListView(
                children: <Widget>[
                  ListTile(
                    leading: const Icon(Icons.add),
                    title: const Text('Add account'),
                  ),
                  ListTile(
                    leading: const Icon(Icons.settings),
                    title: const Text('Manage accounts'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

The drawer menu typically uses the Drawer component as its root widget, which implements a Material-style menu panel. The MediaQuery.removePadding function can remove some of the default padding (such as the padding at the top of the Drawer, which is as high as the phone's status bar). Readers can experiment with different parameters to see the effect. The drawer menu page consists of a top section and a bottom section. The top section includes the user’s avatar and nickname, and the bottom section is a menu list implemented using ListView


36.4 FloatingActionButton

The FloatingActionButton is a special button in the Material Design guidelines, usually floating in a specific position on the page as a shortcut to a frequently used action, such as the "➕" button at the bottom right corner of the page in this example. We can set a FloatingActionButton through the floatingActionButton property of Scaffold, and use the floatingActionButtonLocation property to specify its position on the page. This is quite simple, so we won’t go into more detail.

36.5 Bottom Tab Navigation Bar

We can set a bottom navigation bar using the bottomNavigationBar property of Scaffold. As shown in the example at the beginning of this section, we use the BottomNavigationBar and BottomNavigationBarItem components from the Material library to create a Material-style bottom navigation bar. The implementation code is very simple, so we won’t repeat it here. However, how can we implement the effect shown in Figure?

Flutter (36): Scaffold

The Material library provides a BottomAppBar component, which can be used in conjunction with a FloatingActionButton to achieve the "notch" effect. The source code is as follows:

bottomNavigationBar: BottomAppBar(
  color: Colors.white,
  shape: CircularNotchedRectangle(), // Creates a circular notch in the bottom navigation bar
  child: Row(
    children: [
      IconButton(icon: Icon(Icons.home)),
      SizedBox(), // Leave an empty space in the middle
      IconButton(icon: Icon(Icons.business)),
    ],
    mainAxisAlignment: MainAxisAlignment.spaceAround, // Distribute horizontal space evenly in the bottom navigation bar
  ),
)

As you can see, there is no property to control the notch position in the above code. In fact, the position of the notch depends on the location of the FloatingActionButton. In this case, the position of the FloatingActionButton is:

floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

So, the notch is in the center of the bottom navigation bar.

The shape property of BottomAppBar determines the shape of the notch. The CircularNotchedRectangle creates a circular notch, but we can customize the shape. For example, the Flutter Gallery demo includes a "diamond" shaped notch. Interested readers can explore this on their own.

36.6 Page Body

Finally, we come to the body section of the page. The Scaffold widget has a body property that accepts any Widget. We can pass in any widget. In the next chapter, we will introduce TabBarView, a component that allows page switching. In apps with multiple tabs, TabBarView is typically used as the body of the Scaffold.