Chapters

Hide chapters

Flutter Apprentice

Fourth Edition · Flutter 3.16.9 · Dart 3.2.6 · Android Studio 2023.1.1

Section II: Everything’s a Widget

Section 2: 5 chapters
Show chapters Hide chapters

Section IV: Networking, Persistence & State

Section 4: 6 chapters
Show chapters Hide chapters

6. Advanced Scrollable Widgets
Written by Vincent Ngo

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

You’ve got the hang of scrollable widgets, but there’s so much more to explore. Don’t limit your app to just mobile screens—Flutter excels at adapting to various devices, from phones to tablets, desktops and the web. With Flutter, create an app that’s not only mobile-friendly but effortlessly scales to any screen size. Embrace versatility and go universal.

In this chapter, you’ll delve deeper into the world of scrollable widgets. You’ll learn how to:

  • Create custom scroll effects with the Sliver widget.
  • Make your UI responsive with the GridView widget.

You’ll continue to build out your food app, Yummy, by introducing a new feature: the RestaurantPage. Here, users can tap on a restaurant to explore everything from today’s menu to a gallery of enticing dishes, all displayed responsively.

Heads up: Grab a snack! The sight of food pictures might just work up an appetite.

Here is what the mobile view looks like:

And here’s the experience reimagined for the web:

In a bit your app will look and function beautifully on any device, providing a seamless and responsive experience from mobile devices to the web.

Getting Started

Open the starter project in Android Studio, then run flutter pub get if necessary and run the app.

You’ll see the explore page as shown below:

Note In this chapter you’ll be running the app on mobile and web to test and develop responsive a UI. In Android Studio you can run multiple devices by clicking the drop-down menu as shown below:

New Files in the Project

There are new files in this starter project to help you out. Before you start take a look at them.

Introducing Slivers

Slivers in Flutter are a fundamental part of creating custom scroll effects in a scrollable area. They are a family of widgets that provide various ways to lay out a list of children in a scrolling view.

Qnelowl Bpigetc

Types of Slivers

Slivers operate within the CustomScrollView widget, which allows them to combine different scrolling behaviors in a single scroll view.

Building the Restaurant Page

First you need to set up the RestaurantPage. When users click on a restaurant in the Food Near Me section of your app it will direct them to this page that displays the restaurant’s menu.

// 1
import 'package:flutter/material.dart';
import '../models/restaurant.dart';

// 2
class RestaurantPage extends StatefulWidget {
  final Restaurant restaurant;

  // 3
  const RestaurantPage({
    super.key,
    required this.restaurant,
  });

  @override
  State<RestaurantPage> createState() => _RestaurantPageState();
}

// 4
class _RestaurantPageState extends State<RestaurantPage> {
  // TODO: Add Desktop Threshold
  // TODO: Add Constraint Properties
  // TODO: Calculate Constrained Width
  // TODO: Add Calculate Column Count

  // TODO: Build Custom Scroll View
  // TODO: Build Sliver App Bar
  // TODO: Build Info Section
  // TODO: Build Grid Item
  // TODO: Build Section Title
  // TODO: Build Grid View
  // TODO: Build Grid View Section

  // TODO: Replace build method
  @override
  Widget build(BuildContext context) {
    // 5
    return Scaffold(
      body: Center(
        // TODO: Replace with Custom Scroll View
        child: Text(
          'Restaurant Page',
          style: TextStyle(fontSize: 16.0),),
      ),
    );
  }
}

Navigating to a Restaurant Page

Let’s first implement a way to display a single restaurant.

// 1
Navigator.push(
  // 2
  context,
  // 3
  MaterialPageRoute(
    // 4
    builder: (context) =>
      RestaurantPage(restaurant: widget.restaurant,
    )
  ),
);
import '../screens/restaurant_page.dart';

Building a Sliver for the Restaurant Page

Return to lib/screens/restaurant_page.dart, replace // TODO: Build Custom Scroll View with the following:

CustomScrollView _buildCustomScrollView() {
  return CustomScrollView(
    slivers: [
      // TODO: Add Sliver App Bar
      SliverToBoxAdapter(
          child: Container(
              height: 200.0,
              color: Colors.red,),),
      // TODO: Add Restaurant Info Section
      SliverToBoxAdapter(
          child: Container(
              height: 300.0,
              color: Colors.green,),),
      // TODO: Add Menu Item Grid View Section
      SliverFillRemaining(
          child: Container(
              color: Colors.blue,),),
    ],
  );
}
child: _buildCustomScrollView(),

Building a Sliver App Bar

A SliverAppBar expands to reveal a large image of a restaurant with a circular icon overlayed at the bottom left. As the user scrolls up, the app bar will remain at the top, and shrink the regular app bar size.

SliverAppBar _buildSliverAppBar() {
  // 1
  return SliverAppBar(
    // 2
    pinned: true,
    // 3
    expandedHeight: 300.0,
    // 4
    flexibleSpace: FlexibleSpaceBar(
      // 5
      background: Center(
        // 6
        child: Padding(
          padding: const EdgeInsets.only(
            left: 16.0,
            right: 16.0,
            top: 64.0,
          ),
          // 7
          child: Stack(
            children: [
              // 8
              Container(
                margin: const EdgeInsets.only(bottom: 30.0),
                decoration: BoxDecoration(
                  color: Colors.grey,
                  borderRadius: BorderRadius.circular(16.0),
                  // 9
                  image: DecorationImage(
                    image: AssetImage(widget.restaurant.imageUrl),
                    fit: BoxFit.cover,),),
                  ),
              // 10
              const Positioned(
                bottom:0.0,
                left: 16.0,
                child: CircleAvatar(
                  radius: 30,
                  child: Icon(Icons.store, color: Colors.white,),
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

Integrate the Sliver App Bar

Now add the app bar to your custom scroll view. Locate // TODO: Add Sliver App Bar and replace it and SliverToBoxAdapter with the following:

_buildSliverAppBar(),

Building the Restaurant Info Section

After adding a sliver containing the restaurant’s information such as the name, address, rating, distance and attributes your app bar will look like this:

// 1
SliverToBoxAdapter _buildInfoSection() {
  // 2
  final textTheme = Theme.of(context).textTheme;
  // 3
  final restaurant = widget.restaurant;
  // 4
  return SliverToBoxAdapter(
    // 5
    child: Padding(
      padding: const EdgeInsets.all(16.0),
      // 6
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 7
          Text(restaurant.name, style: textTheme.headlineLarge,),
          Text(restaurant.address, style: textTheme.bodySmall,),
          Text(
            restaurant.getRatingAndDistance(),
            style: textTheme.bodySmall,),
          Text(restaurant.attributes, style: textTheme.labelSmall,),
        ],
      ),
    ),
  );
}
_buildInfoSection(),

Introducing GridView

GridView is a 2D array of scrollable widgets. Similar to its linear counterpart, the ListView, it supports both horizontal and vertical scrolling, but it arranges its children in a grid format, which is perfect for displaying multiple items in a clean, organized layout.

cqavm uyeb meur epud

GridView Key Parameters

Here are some parameters you should pay attention to:

Understanding the Cross and Main Axis

What’s the difference between the main and cross axis? Remember that Columns and Rows are like ListViews, but without a scroll view.

Zaw gaix ulom ppund ukiv

neun eziv Fivovd kmumc ujem

Understanding Grid Delegates

Grid delegates help you figure out the spacing and the number of columns to use to lay out the children in a GridView.

Building the Grid View Section

You’ll use the GridView widget to display a collection of items on a restaurant’s menu. GridView is a great widget to make your app responsive on different screens.

Building the Grid Item

Still within restaurant_page.dart locate // TODO: Build Grid Item and replace it with the following:

Widget _buildGridItem(int index) {
  final item = widget.restaurant.items[index];
  return InkWell(
    onTap: () {
      // Present Bottom Sheet in the future.
    },
    child: RestaurantItem(item: item),
  );
}
import '../components/restaurant_item.dart';

Building the Section Title

Next, locate // TODO: Build Section Title and replace it with the following:

Widget _sectionTitle(String title) {
  return Padding(
    padding: const EdgeInsets.all(8.0),
    child: Text(
      title,
      style: const TextStyle(
        fontSize: 24,
        fontWeight: FontWeight.bold,),
    ),
  );
}

Building the Grid View

Replace // TODO: Build Grid View with the following:

// 1
GridView _buildGridView(int columns) {
  // 2
  return GridView.builder(
    padding: const EdgeInsets.all(0),
    // 3
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      mainAxisSpacing: 16,
      crossAxisSpacing: 16,
      childAspectRatio: 3.5,
      crossAxisCount: columns,
    ),
    // 4
    itemBuilder: (context, index) => _buildGridItem(index),
    // 5
    itemCount: widget.restaurant.items.length,
    // 6
    shrinkWrap: true,
    // 7
    physics: const NeverScrollableScrollPhysics(),
  );
}

Building the Grid View Sliver Section

Find // TODO: Add Desktop Threshold and replace it with the following:

static const desktopThreshold = 700;
int calculateColumnCount(double screenWidth) {
  return screenWidth > desktopThreshold ? 2 : 1;
}
// 1
SliverToBoxAdapter _buildGridViewSection(String title) {
  // 2
  final columns = calculateColumnCount(MediaQuery.of(context).size.width);
  // 3
  return SliverToBoxAdapter(
    // 4
    child: Container(
      padding: const EdgeInsets.all(16.0),
      // 5
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 6
          _sectionTitle(title),
          // 7
          _buildGridView(columns),
        ],
      ),
    ),
  );
}
_buildGridViewSection('Menu'),

Implementing a Responsive Menu

Crafting a responsive restaurant menu for web applications is a pivotal task that strikes a balance between optimal use of screen real estate, readability, and visual appeal.

Implementing Responsive Design

Next, you’ll delve into the technical implementation to achieve a responsive and visually pleasant menu for web users.

static const double largeScreenPercentage = 0.9;
static const double maxWidth = 1000;
double _calculateConstrainedWidth(double screenWidth) {
  return (screenWidth > desktopThreshold
          ? screenWidth * largeScreenPercentage //
          : screenWidth)
      .clamp(0.0, maxWidth);
}
@override
Widget build(BuildContext context) {
  final screenWidth = MediaQuery.of(context).size.width;
  final constrainedWidth = _calculateConstrainedWidth(screenWidth);

  return Scaffold(
    body: Center(
      child: SizedBox(
        width: constrainedWidth,
        child: _buildCustomScrollView(),
      ),
    ),
  );
}

Key Points

  • Slivers allow building intricate scrolling layouts with CustomScrollView and various sliver widgets.
  • With GridView you can create grid layouts with customizable columns and spacing.
  • SliverToBoxAdapter enables the integration of non-sliver widgets into sliver lists.
  • Manage scroll behavior and direction with properties like physics.
  • Embed grid views within sliver lists for complex scrollable structures.
  • Use MediaQuery to create responsive grid layouts with GridView, adjusting the number of columns based on the screen size.

Where to Go From Here?

Now that you’ve a grasp of slivers and grid views in Flutter, you can create complex and custom scrollable layouts that look good and perform well on a wide range of devices. Slivers enable you to make scrollable areas in your app that look and behave exactly how you want.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now