iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🐶

Having Fun with Flutter's AppBar

に公開

Hello and good evening, I'm Sugit ٩( ᐛ )و
Today's theme is AppBar.
Rather than a practical topic, this is more about having fun and playing around with various things.

Do you use AppBar?

Not limited to Flutter, but I feel that AppBar is actually a slightly difficult component to handle. The AppBar provided by Flutter is built according to Material Design guidelines, so it's very well-made and is a Widget that makes sense for many apps to adopt.

First, let's look at a basic AppBar.

basic_appbar

If you have ever built a Flutter app, you're likely very familiar with this.
Now, what role does this AppBar play?

The Material Design guidelines describe how an AppBar should be as follows:

The top app bar provides content and actions related to the current screen. It’s used for branding, screen titles, navigation, and actions.

https://material.io/components/app-bars-top

In other words, it displays information or provides user actions based on what's shown on the app's main screen. As you can see from this, it's not the main star of the app. I consider it a supplementary element used to properly provide the main content being displayed to the user.

Does your app need an AppBar?

It might be worth reconsidering this. First, think about "whether it's really necessary." For what purpose is that AppBar there?

😎 ... (It exists to inform the user via text which page they are currently on)
🙄 ... (That might be fine for sub-pages after navigation, but what about the main page? If you're using a BottomNavigationBar, isn't looking at that enough?)
😎 ... (I want to place a hamburger menu to show a drawer)
🙄 ... (In that case, do you really need the Title part in the center...?)

There are various cases, but try running through a conversation like the one above (↑) in your head. If you conclude that you still need an AppBar, then please check the following explanations to see what kind of expressions are possible with AppBar and try creating a better one.

Let's try matching the background colors of the AppBar and the Body

In the Theme, the AppBar color is defined in AppBarTheme.backgroundColor. By default, ColorScheme.primary is applied, so many people probably remember that it changes when you change the primarySwatch in the MaterialApp theme. To be precise, if "backgroundColor is not specified in the AppBar" AND "AppBarTheme is not specified in the MaterialApp's Theme," the default primary color is applied. This is mentioned if you read the documentation carefully. I wrote an article explaining this in detail, so please refer to it.

https://zenn.dev/sugitlab/articles/bef3a05963680a

theme: ThemeData(
  primarySwatch: Colors.cyan,
),

For example, if you specify the cyan color in primarySwatch like this, it looks like this:
appbar_primary_cyan

The AppBar basically always exists at the top of the screen. Therefore, if the AppBar is not used effectively, some users might perceive it as an "annoying part taking up valuable screen space." For example, nobody likes thick bezels on a display, right? If a user thinks of it as an "unnecessary part," that AppBar becomes a thick bezel. That's sad. In such cases, try matching the background color and the AppBar color.

Let's take a look.

white_appbar

Making it completely white gives it an iOS feel. I explained this in a previous article.
Note that this isn't about trying to make Material Design look like iOS. If that were the case, you should just use Cupertino Widgets from the start. The purpose here is to create a sense of unity across the whole screen by blurring the boundary with the Body.

In the sense of blurring the boundary, it's interesting to try adjusting the AppBar's Elevation.

  • elevation: 0
    elevation_zero_appbar

  • elevation: 2
    elevation_two_appbar

  • elevation: 10
    elevation_ten_appbar

For example, the Google Ads app uses a similar expression[1].

google_ads_ui

The benefits of using a white-based theme for everything include:

  • Making dark mode support easy to understand
  • Making accent colors stand out easily

While you want to use accent colors for important parts of the app, if you use that color in the AppBar or use a different color there, the message of the part you really want to convey might be weakened.

Of course, it's possible to create an effective AppBar without making it white-based. Let's look at another color immediately. For example, I used a strong color that might seem difficult to handle (Colors.purple). Since it's hard to imagine without any main content, I've placed a single card.

theme: ThemeData(
  primarySwatch: Colors.purple,
  scaffoldBackgroundColor: Colors.purple[600],
),

purple_appbar_and_body

By specifying similar colors for the background of the AppBar and the Scaffold, you can see that the main content really stands out. This can also be called a technique for effectively using the app's theme color.

I based this on the Nubank example in the Flutter Showcase.

https://flutter.dev/showcase

Now, while I understand that using similar colors for the background makes the main content stand out, I still feel it's a bit "meh." I'm the type of person who finds solid colors across a large area a bit noisy to the eyes.

So, I'll try applying a gradient that makes it feel like there's a highlight at the boundary between the AppBar and the Body. By doing so, I think it becomes easier on the eyes, or rather, it creates an impressive atmosphere in a good way.

purple_appbar_and_body_with_gradient

The gradient is achieved by wrapping the Body in a Container Widget and applying a BoxDecoration.

body: Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.bottomCenter,
      end: Alignment.topCenter,
      colors: [Colors.purple[900], Colors.purple[700], Colors.purple[500]],
    ),
  ),
  child: MyWidget(),
),

I've also explained patterns where strong colors are used in the background, but this technique drastically changes the overall atmosphere of the app and is a strong expression that can become the image of the app itself. By identifying where to use it and using it appropriately, you can create an impressive app.

Why not try making the AppBar slightly transparent?

I've been discussing how unity between the AppBar and the main content is important. Another approach to achieve this sense of unity is making the AppBar transparent. This helps convey the idea that "the AppBar is not something that gets in the way of the main content."

This technique is often adopted in apps that have a lot of main content and require scrolling (such as LINE). Let's take a look at the finished result.

transparent_appbar_gif

To make the AppBar transparent, you can simply specify a transparent color as the background color. While the example below uses a fully transparent color (no color), you could use something like Colors.green.withOpacity(0.5) to create a semi-transparent green AppBar.

However, this alone just makes it an AppBar with no background color; it doesn't provide a true "transparent feel." This is because the scrolling end position of the main content is at the bottom of the AppBar.

In the Scaffold Widget, the default rule is that the drawable area of the Body is restricted to the space below the AppBar. Therefore, no matter how much you scroll, the content stops being rendered once it reaches the bottom of the AppBar.

transparent_failed

To remove this rendering restriction, the Scaffold Widget provides a setting called extendBodyBehindAppBar.

https://api.flutter.dev/flutter/material/Scaffold/extendBodyBehindAppBar.html

home: Scaffold(
        appBar: AppBar(
          leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
          title: Text('HelloWorldApp'),
          centerTitle: true,
          actions: [
            IconButton(icon: Icon(Icons.search), onPressed: () {}),
            IconButton(icon: Icon(Icons.more_vert), onPressed: () {}),
          ],
          elevation: 0,
          backgroundColor: Colors.transparent,
        ),
        extendBodyBehindAppBar: true, // <--- Here
	body: SingleChildScrollView(
	  // Omitted
	),
     ),

By enabling this setting, the Body's drawable area is configured to ignore the AppBar. As a result, rendering becomes possible up to the top edge of the device, and content will pass behind the transparent AppBar.

For example, with Green + Transparency (Colors.green.withOpacity(0.3)), it looks like this.
green_transparent_appbar

There is one point to be careful about. Setting extendBodyBehindAppBar to true shifts the initial placement of the top edge to the top of the device. Consequently, the layout of the Body's Widgets shifts upwards. In other words, the main area that would normally start from under the AppBar now begins at the top edge of the device. In most cases, you'll want the main content to initially be below the AppBar and only go behind it when scrolling. In that case, use a SizedBox Widget or similar at the very top of the scrollable content to insert space. You can calculate the height of the space based on the AppBar's toolBarHeight or similar properties.

Creating an AppBar with Photos or Illustrations

If you want to make your app more impressive, why not try using illustrations or photos for the background of your AppBar? For example, in a tourist guide app, you could use a representative photo of that destination; for an app used by a school or community, a photo of that community; or for an event app, an image representing the event.

It can be effective when you want users to feel like they've dove into that world the moment they launch the app.

Now, regarding how to implement it, we'll use the flexibleSpace property of the AppBar.
https://api.flutter.dev/flutter/material/AppBar/flexibleSpace.html

appBar: AppBar(
          leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
          title: Text('Welcome to Tokyo'),
          centerTitle: true,
          actions: [
            IconButton(icon: Icon(Icons.search), onPressed: () {}),
          ],
          elevation: 1,
          flexibleSpace: Image.network( // <--- Specify it here.
            'https://images.unsplash.com/photo-1513407030348-c983a97b98d8?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1352&q=80',
            fit: BoxFit.cover,
          ),
          toolbarHeight: 100, // <--- Adjusting the height here will also change the atmosphere
          backgroundColor: Colors.transparent,
        ),

The photo was borrowed from Unsplash. You might want to adjust the toolbarHeight depending on how the photo looks.

photo_appbar

This AppBar really makes you want to go sightseeing in Tokyo!! I can't wait for the days when we can travel freely again.

Note that while I used Image.network() this time, you should be a bit more clever when fetching images from the internet. Specifically, you should use caching. In most cases, re-fetching the image is unnecessary even when a widget rebuild occurs. By utilizing caching appropriately, you can avoid performance degradation. You can easily achieve this using the following package.

https://pub.dev/packages/cached_network_image

Hiding the AppBar as you scroll

If you try using several apps on your device, you'll find that many of them implement a feature where the AppBar hides (or shrinks) as you scroll. This technique is often used when the main content consists of a large list that requires scrolling.

This kind of behavior is also introduced in the Material Design guidelines and can be considered a common method.

https://material.io/components/app-bars-top#behavior

appbar_scroll_behavior

Flutter provides a widget to achieve this: SliverAppBar. Other widgets often used in combination with SliverAppBar are collectively referred to as Slivers.

https://api.flutter.dev/flutter/material/SliverAppBar-class.html

Although the SliverAppBar widget has "AppBar" in its name, it cannot be used as the appBar of a Scaffold. As you can see from its properties, the Scaffold's appBar requires a PreferredSizeWidget.

preferedsizewidget

However, SliverAppBar is a regular widget. Based on this, if you want to implement behavior where the AppBar hides according to scrolling, you must leave the appBar property of the Scaffold empty and build everything within the body. Also, while I said SliverAppBar is a regular widget, there is actually a rule that Sliver-type widgets must be used with other Sliver-type widgets. This is a constraint imposed by CustomScrollView, which implements the scrolling behavior when using Sliver widgets. This means you cannot simply place a standard Container widget directly under a SliverAppBar without any processing. If you want to place one, you need to use techniques like using a SliverToBoxAdapter. Let's look at them in order.

https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html

The API documentation above starts with the following explanation:

A ScrollView that creates custom scroll effects using slivers.

The important part is using slivers. It's specified for Slivers.

Now, let me introduce some representative Sliver-type widgets.

  • SliverAppBar
    • An AppBar that hides smoothly.
  • SliverList
    • A ListView.
  • SliverGrid
    • A GridView. For vertical x horizontal grids.
  • SliverToBoxAdapter
    • Used when you want to sneak a regular(?) widget inside a list of Sliver widgets.

These are the ones you'll primarily use. In addition, you can achieve more effective effects by combining the following widgets:

  • SliverPadding
  • SliverSafeArea
  • SliverOpacity
  • SliverAnimatedList

Now, let's build a main screen using SliverAppBar + SliverList.

First of all, use CustomScrollView.

home: Scaffold(
  body: CustomScrollView(
    slivers: [],
  ),
),

CustomScrollView has many properties for adjusting scroll-related settings, but basically, you don't need to touch them when starting out. That means the only place to work on is slivers. As you can tell from the "s" in sliver"s", you list and use Sliver-type widgets here. You can use it with the same feeling as a Column widget.

Now, let's implement SliverAppBar and SliverList as slivers.

body: CustomScrollView(
  slivers: [
    SliverAppBar(
      leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
      title: Text('Hello Sliver World'),
      actions: [
	IconButton(icon: Icon(Icons.search), onPressed: () {}),
      ],
    ),
    SliverList(
      delegate: SliverChildListDelegate(
	[
	  MyWidget(1),
	  MyWidget(2),
	  // .... feel free to add more
	],
      ),
    ),
  ],
),

When you run this, it is displayed like this. When you scroll, it looks like this.

Now you have completed an effective SliverAppBar. Adjusting SliverAppBar from this basic point is very fun.

  • collapsedHeight
  • expandedHeight
  • flexibleSpace

By using these three well, you can create an advanced version of the Creating an AppBar with Photos or Illustrations introduced earlier.

Here it is:

It makes a good first impression using an impressive photo, and when it's time to view the app's content, it hides cleanly. How wonderful.

While I used SliverList this time, SliverGrid and SliverToBoxAdapter are also commonly used. Grid is self-explanatory, but SliverToBoxAdapter is convenient because it can have a regular(?) widget as its child.

Techniques to make the AppBar look like it's nicely curved

AppBar shapes are usually rectangular, but you can actually add a bit of an arrangement. I'll show you a few examples.

round_appbar

I tried rounding the corners of the AppBar. The implementation is as follows:

appBar: AppBar(
  leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
  title: Text('Hello World', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
  shape: RoundedRectangleBorder( // <--- Here
    borderRadius: BorderRadius.only(
      bottomLeft: Radius.circular(30),
      bottomRight: Radius.circular(30),
    ),
  ),
),

To change the shape of the AppBar, the shape property is used. I specified RoundedRectangleBorder for shape. This allows you to create a boundary like a rectangle with its corners rounded. It's similar to the shape of a rounded button.

The borderRadius is what specifies the roundness of the corners. BorderRadius determines "which corner" and "what kind of curve" to apply. This time, since the top of the screen may be hidden depending on the shape of the smartphone, I didn't specify rounding there and only rounded the bottom left and right. Radius.circular() is used to carve out the corner in a circular shape. The larger the number inside, the stronger the curve becomes.

Now that I've made it, my reaction is like "Oh, so you can do something like this." To be honest, I just wanted to decorate it a bit, and I feel like it's not really that great. However, I feel like these kinds of curves could look good if used well. So, although it's a bit of a heavy implementation, I've created an example with a unique taste.

home: Scaffold(
        appBar: AppBar(
          leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
          title: Text('Hello World', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
              bottomRight: Radius.elliptical(90, 30),
            ),
          ),
          elevation: 0,
        ),
        body: Align(
          alignment: Alignment.topCenter,
          child: Stack(
            children: [
              Container(
                height: 500,
                color: Theme.of(context).primaryColor,
              ),
              Container(
                height: 500,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(200),
	          ),
                  color: Theme.of(context).scaffoldBackgroundColor,
                ),
              ),
	      // Card implementation part
            ],
          ),
        ),
      ),

Executing this results in this!

curve_appbar_to_body

A gentle curve is drawn from the AppBar toward the Body. I don't know what kind of meaning this has, though. It looks somewhat stylish, doesn't it? It might be good for practice.

The technique being used here is Stack.

Stack is a layout widget for overlaying multiple widgets. This time, I first placed the following simple Container as a base:

Container(
  height: 500,
  color: Theme.of(context).primaryColor,
),

The height is specified somewhat arbitrarily as the height where the curve might end. Since I wanted the color to be the same as the AppBar, I used primaryColor for now. Change this depending on how you specify the color of the AppBar.

Then, place the following widget on top of this colored box:

Container(
  height: 500,
  decoration: BoxDecoration(
    borderRadius: BorderRadius.only(
      topLeft: Radius.circular(200),
    ),
    color: Theme.of(context).scaffoldBackgroundColor,
  ),
),

This one has the same height as the previous base, with a large curve on the top-left corner. Furthermore, by setting the color to scaffoldBackgroundColor, it becomes the same color as the rest of the bottom area exceeding the height of 500, blending into the scene.

You can place the main content on top of these two stacked containers.

Now, one more thing. I introduced a method earlier to make it look effective by matching the AppBar and Body colors while placing a white-based Container. With the same technique, you can create an atmosphere as if the AppBar itself is curved.

home: Scaffold(
        appBar: AppBar(
          leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
          title: Text('Hello World', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
              bottomRight: Radius.elliptical(90, 30),
            ),
          ),
          elevation: 0,
        ),
        body: Stack(
          children: [
            Align(
              alignment: Alignment.bottomCenter,
              child: Container(
                height: 600,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(60),
                    topRight: Radius.circular(60),
                  ),
                  color: Colors.grey[100],
                ),
              ),
            ),
            // Card implementation part
          ],
        ),
      ),

Executing this results in this:

curve_body

This is actually very simple to achieve:

  • Make the background of the body the same color as the AppBar
  • Set the AppBar's elevation to zero
  • Place a white-based item aligned to the bottom
  • Place the main content on top of these using a Stack

That's it. Stack is fun, isn't it?

I think adopting this example for production requires some courage, but if you can use it effectively, it might become something that catches the eye.

Don't hesitate to use useful packages

The fastest way to find Flutter packages is to search on pub.dev.

https://pub.dev/

Try entering appbar in the search field. There are many packages, so it's fun just to look at them. Many packages have their GitHub repositories open to the public, so looking at the implementation there is very educational.

Now, as a type of thing that wasn't in what I've introduced so far, let's introduce the search bar. Cases where you want to search through a lot of data are required in many apps. Since it can be a bit of a hassle to implement from scratch yourself, it might be a good idea to rely on a package.

https://pub.dev/packages/flutter_search_bar

https://pub.dev/packages/floating_search_bar

https://pub.dev/packages/material_floating_search_bar

Conclusion

I've played around with various things regarding AppBar.
It was fun!!
I'll add more when I find new interesting ways to show it.

Also, the various AppBars introduced here are available at this repository, so please refer to them if you'd like.
https://github.com/sugitlab/flutter_ui_cookbook/tree/main/lib/appbar

I hope this helps anyone having trouble with AppBars.

Sugit ٩( ᐛ )و

脚注
  1. For Flutter apps, it's good to look at the Showcase -> https://flutter.dev/showcase ↩︎

GitHubで編集を提案

Discussion