Widgets – the promised land of Flutter developers who want their work to be easier, their code to be neat, and UI spotless! Widgets are the main building blocks of Flutter, a framework written in Dart language. That is why knowledge of various widgets is crucial for Flutter devs. Some of them aren’t used frequently, with others you will meet almost in every digital product you are about to develop. When you become aware of possible solutions then it is time to use them well! Every app built in Flutter is like a widget tree. If your goal is to create beautiful UIs for your apps in Flutter, you must get to know them well. So let’s dive into basic Flutter widgets that will be useful to develop your next mobile app!  

1. Align

This is a widget that aligns its child within itself and optionally sizes itself based on the child’s dimensions. For example, to align a box at the bottom right, you would pass this box a tight constraint that is bigger than the child’s natural size, with an alignment of Alignment.bottomRight. This widget can be as big as possible if its dimensions are constrained and widthFactor and heightFactor are null. If a dimension is unconstrained and the corresponding size factor is null then the widget will match its child’s size in that dimension. If a size factor is non-null then the corresponding dimension of this widget will be the result of the child’s dimension and the size factor. For example, if widthFactor is 2.0 then the width of this widget will always be twice its child’s width.

Let’s see the example. The Align widget here uses one of the defined constants from Alignment, Alignment.topRight. This places the FlutterLogo in the top right corner of the parent blue Container.

Center(
  child: Container(
    height: 120.0,
    width: 120.0,
    color: Colors.blue[50],
    child: const Align(
      alignment: Alignment.topRight,
      child: FlutterLogo(
        size: 60,
      ),
    ),
  ),
)

Easy, right?

2. AppBar

Usually, it is the topmost component of the App. The AppBar is commonly used in every application. Most of the time, the Leading Icon(drawer menu), title and the actions icons are the component that includes an AppBar. The AppBar insets its content based on the ambient MediaQuery’s padding, to evade system UI intrusions. It’s taken care of by Scaffold when applied in the Scaffold.appBar property. When animating an AppBar, unexpected MediaQuery changes (as is common in Hero animations) may cause the content to suddenly jump. Then wrap the AppBar in a MediaQuery widget, and adjust its padding such that the animation is smooth. The sample code below presents two simple actions. The first opens a SnackBar, and the second action navigates to a new page.

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('AppBar Demo'),
      actions: <Widget>[
        IconButton(
          icon: const Icon(Icons.add_alert),
          tooltip: 'Show Snackbar',
          onPressed: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('This is a snackbar'))
            );
          },
        ),
        IconButton(
          icon: const Icon(Icons.navigate_next),
          tooltip: 'Go to the next page',
          onPressed: () {
            Navigator.push(context, MaterialPageRoute<void>(
              builder: (BuildContext context) {
                return Scaffold(
                  appBar: AppBar(
                    title: const Text('Next page'),
                  ),
                  body: const Center(
                    child: Text(
                      'This is the next page',
                      style: TextStyle(fontSize: 24),
                    ),
                  ),
                );
              },
            ));
          },
        ),
      ],
    ),
    body: const Center(
      child: Text(
        'This is the home page',
        style: TextStyle(fontSize: 24),
      ),
    ),
  );
}

3. Center

The Center class is a widget that centers its child within itself. It works similarly to the Align widget. It can be as big as possible if its dimensions are constrained and widthFactor and heightFactor are null. If a dimension is unconstrained and the corresponding size factor is null then the widget will match its child’s size in that dimension. If a size factor is non-null then the corresponding dimension of this widget will be the product of the child’s dimension and the size factor. For example, if widthFactor is 2.0 then the width of this widget will always be twice its child’s width.

4. Container

This one is a really convenient widget – it combines common painting, positioning, and sizing widgets. A container first surrounds the child with padding (inflated by any borders present in the decoration) and then applies additional constraints to the padded extent (incorporating the width and height as constraints, if either is non-null). The container is then surrounded by additional empty space described from the margin. During painting, the container first applies the given transform, then paints the decoration to fill the padded extent, then it paints the child, and finally paints the foregroundDecoration, also filling the padded extent.

Containers with no children try to be as big as possible unless the incoming constraints are unbounded, in which case they try to be as small as possible. 

By default, containers return false for all hit tests. If the color property is specified, the hit testing is handled by ColoredBox, which always returns true. If the decoration or foregroundDecoration properties are specified, hit testing is handled by Decoration.hitTest.

Let’s look at the example, where we have a 48×48 amber square (placed inside a Center widget in case the parent widget has its own opinions regarding the size that the Container should take), with a margin so that it stays away from neighboring widgets:

Center(
  child: Container(
    margin: const EdgeInsets.all(10.0),
    color: Colors.amber[600],
    width: 48.0,
    height: 48.0,
  ),
)

To learn more, watch this video:

5. Icon

This is a Flutter’s graphical icon widget is drawn with a glyph from a font defined in an IconData such as material’s predefined IconDatas in Icons. You must remember that icons aren’t interactive. You have to have an ambient Directionality widget when using Icon. Typically this is introduced automatically by the WidgetsApp or MaterialApp. Moreover, this widget assumes that the rendered icon is squared. Non-squared icons can render inaccurately.

The sample below shows how to create a Row of Icons in various colors and sizes. The first Icon uses a semanticLabel to announce in accessibility modes like TalkBack and VoiceOver.

Row(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: const <Widget>[
    Icon(
      Icons.favorite,
      color: Colors.pink,
      size: 24.0,
      semanticLabel: 'Text to announce in accessibility modes',
    ),
    Icon(
      Icons.audiotrack,
      color: Colors.green,
      size: 30.0,
    ),
    Icon(
      Icons.beach_access,
      color: Colors.blue,
      size: 36.0,
    ),
  ],
)

You can take a look at the available icons here.

6. Image

This widget displays an image, I know, that was a huge surprise! 😉 Several constructors are provided for the various ways that an image can be specified:

  • new Image, for obtaining an image from an ImageProvider.
  • new Image.asset, for obtaining an image from an AssetBundle using a key.
  • new Image.network, for obtaining an image from a URL.
  • new Image.file, for obtaining an image from a File.
  • new Image.memory, for obtaining an image from a Uint8List.

JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP are the supported formats you can use. Additional formats may be supported by the underlying platform. Flutter will attempt to call platform API to decode unrecognized formats, and if the platform API supports decoding the image Flutter will be ready to render it.

To automatically perform pixel-density-aware asset resolution, specify the image using an AssetImage and make sure that a MaterialApp, WidgetsApp, or MediaQuery widget exists above the Image widget in the widget tree.

That is it, now let’s see a sample when we use the Image.network constructor to display an image from the internet, to be more precise from our website. 

Image.network('https://applover.com/wp-content/themes/applover/assets/images/logos/logo-clutch.png')

What is important, if you want to use a .svg file, you need this package.

7. Padding

This is a Flutter widget that inserts its child with the given padding. When passing layout constraints to its child, padding shrinks the constraints by the given padding, causing the child to layout at a smaller size. Padding then sizes itself to its child’s size, inflated by the padding, effectively forming empty space around the child. This snippet produces “Hello World!” Text inside a Card that is indented by sixteen pixels in each direction.

const Card(
  child: Padding(
    padding: EdgeInsets.all(16.0),
    child: Text('Hello World!'),
  ),
)

8. Placeholder

How to draw a box that represents where other widgets will one day be added? Use a Placeholder! This widget is useful during development to indicate that the interface isn’t yet finished. By default, the placeholder is sized to fit its container. If the placeholder is in an unbounded space, it will size itself according to the given fallbackWidth and fallbackHeight.

If you want to discover more about coding with the Placeholder widget, watch this video. 

9. Scaffold

This widget is responsible for implementing the basic material design visual layout structure.

The Scaffold class provides APIs for showing drawers and bottom sheets. To display a persistent bottom sheet, obtain the ScaffoldState for the current BuildContext via Scaffold.of and use the ScaffoldState.showBottomSheet function.

The example below displays a Scaffold with a blueGrey backgroundColor, body and FloatingActionButton. The body is a Text placed in a Center in order to center the text within the Scaffold. The FloatingActionButton is connected to a callback that increments a counter.

int _count = 0;

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Sample Code'),
    ),
    body: Center(
      child: Text('You have pressed the button $_count times.')
    ),
    backgroundColor: Colors.blueGrey.shade200,
    floatingActionButton: FloatingActionButton(
      onPressed: () => setState(() => _count++),
      tooltip: 'Increment Counter',
      child: const Icon(Icons.add),
    ),
  );
}

10. Scrollbar

Scroll bars show the user how far they’ve scrolled, and they allow things like jumping to a particular point in the list. Use the Scrollbar widget to show a Scrollbar. Wrap scrollbar around any scrollbar widget like a listview, gridview or Custom Scrollview. Make sure the scrollable widget is finite or else make sure the item count of the widget is finite. Otherwise, the Scrollbar doesn’t know how far to the bottom is, and therefore the scrollbar will not be visible. By default, the scrollbar will hide when it’s not in use. You can force it to always be visible with isAlwaysShown. The Scrollbar works both vertically and horizontally. Moreover, you can style them any way you want using the Scrollbar theme. Let’s look at the sample, that shows an app with two scrollables in the same route. Since by default, there is one PrimaryScrollController per route, and they both have a scroll direction of Axis.vertical, they would both try to attach to that controller. The Scrollbar cannot support multiple positions attached to the same controller, so one ListView, and its Scrollbar have been given a unique ScrollController.

final ScrollController _firstController = ScrollController();

@override
Widget build(BuildContext context) {
  return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        return Row(
          children: <Widget>[
            SizedBox(
                width: constraints.maxWidth / 2,
                // Only one scroll position can be attached to the
                // PrimaryScrollController if using Scrollbars. Providing a
                // unique scroll controller to this scroll view prevents it
                // from attaching to the PrimaryScrollController.
                child: Scrollbar(
                  isAlwaysShown: true,
                  controller: _firstController,
                  child: ListView.builder(
                      controller: _firstController,
                      itemCount: 100,
                      itemBuilder: (BuildContext context, int index) {
                        return Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text('Scrollable 1 : Index $index'),
                        );
                      }
                  ),
                )
            ),
            SizedBox(
                width: constraints.maxWidth / 2,
                // This vertical scroll view has not been provided a
                // ScrollController, so it is using the
                // PrimaryScrollController.
                child: Scrollbar(
                  isAlwaysShown: true,
                  child: ListView.builder(
                      itemCount: 100,
                      itemBuilder: (BuildContext context, int index) {
                        return Container(
                            height: 50,
                            color: index.isEven ? Colors.amberAccent : Colors.blueAccent,
                            child: Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: Text('Scrollable 2 : Index $index'),
                            )
                        );
                      }
                  ),
                )
            ),
          ],
        );
      }
  );
}

11. SizedBox 

This widget adds a space of a specific size for a box. With it, you can specify the height and width of the spacing. If you provide a child, the widget forces it to have a specific width and/or height. These values will be neglected if this widget’s parent doesn’t permit them. For example, this occurs if the parent is the screen (forces the child to be the same size as the parent), or another SizedBox (forces its child to have a particular width and/or height). This can be solved by wrapping the child SizedBox in a widget that allows it to be any size up to the size of the parent, such as Center or Align, mentioned above.

If either the width or height is null, this widget will try to size itself to match the child’s size in that dimension. If the child’s size depends on the size of its parent, the height and width must be provided.

If not given a child, SizedBox will try to size itself as close to the specified height and width as possible given the parent’s constraints. If height or width is null or unspecified, it will be treated as zero.

See the example of the snippet that makes the child widget (a Card with some Text) have the exact size 200×300, parental constraints permitting.

const SizedBox(
  width: 200.0,
  height: 300.0,
  child: Card(child: Text('Hello World!')),
)

Contact

Do you want to find out more about Flutter Development?

Talk to us!

Widgets are your best Flutter friends

So now you know basic Flutter widgets and understand how they work. Hopefully, it was useful and you can use them to create beautiful apps faster! Flutter provides a collection of the visual, structural, platform, and interactive widgets that are constantly developed. You can always learn about them by browsing them in the widget index.