Flutter Crash Course: "02 - Layout Basics"

Summary

we'll take a first look at the mockups we want to implement for this course for our "Tourism & Co." app. We'll start to implement the "detail screen" of the app, where an image is shown at the top along with some text below. We'll cover some Flutter layout concepts to ensure our page's content is laid out properly.

In this lesson we'll cover: working with layout using Column, mainAxisAlignment and crossAxisAlignment

The Code for This Lesson

Check out the tourismandco repo's step/step02 branch for the code we'll cover in this lesson.

What We're Working Toward

We'll be implementing three text sections which will be part of our "Location Detail" screen for the Tourism & Co. app. Later, these text sections will be dynamic but for now, we'll just create three so that text sections of our screen has its own widget.

tourismandco-screenshot1

Basic Layout Concepts in Flutter

Layout controls the way content in your is constraied or expands in a given area. Your app can be shown on various screen sizes, can be rotated and content can change dynamically. Content can be images, text, buttons or really anything. It's important to ensure this content is nicely contained to the user.

The most common layout pattern, especially for mobile phones in particular, is a "Column" of content, where each item in the column flows from top to bottom. For this type of layout, Flutter defines something called the "main axis", which for a Column is a vertical line going through the widget. For other widgets, such as the Row widget, this "main axis" also exists, but is instead a horizontal row going from left to right of the widget.

We also have the converse of "main axis" which is the "cross axis". In other words, this is the "other" axis of a given layout widget. So for Column, the "cross axis" is a horizontal line going through each child of Column. For Row, it'd instead be a vertical line going through each child.

Layout using Column, mainAxisAlignment and crossAxisAlignment

  • Take a look at https://github.com/seenickcode/tourismandco/*.png in this repo to see the screenshots of what we'll implement in this course.
  • Rename home/home.dart to location_detail/location_detail.dart as well as the class name. This will our "location detail" screen of our tourism app.
  • Let's implement four individual sections of our screen, where the first will soon contain an image and the rest will contain some text.

Using the Column Widget

  • Implement _bannerContainer() and _textSectionContainer(), each with a simple container and BoxDecoration.
  • This way, we can visually see how layout will affect our content.
  • Add a Column widget to the main body.
  • The Column widget is used to present content in a single column, where each child widget is laid out from top to bottom.
  • mainAxisAlignment allows us to control how each child of a Column is laid out from top to bottom. The "main axis" is essentially a vertical line going through the center of our Column.
  • crossAxisAlignment controls how content is aligned for each individual child of the Column.
// location_detail/location_detail.dart

// WARNING: not the final implementation of this lesson

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

class LocationDetail extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Hello'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              decoration: BoxDecoration(
                color: Colors.red,
              ),
              child: Text('hi'),
            ),
              Container(
              decoration: BoxDecoration(
                color: Colors.green,
              ),
              child: Text('hi'),
            ),
              Container(
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
              child: Text('hi'),
            ),
          ],
        ));
  }
}

Here we define a few text sections, using the Column widget. We're showing a block of color using the Container widget, where each block is a different color. This will help us see the different layout options we have available to us by changing mainAxisAlignment and crossAxisAlignment.

Notice what happens when you use:

  • MainAxisAlignment.spaceEvenly
  • MainAxisAlignment.end
  • CrossAxisAlignment.center
  • CrossAxisAlignment.end
  • Try removing mainAxisAlignment and crossAxisAlignment params altogether!

Implementing our own Parameterized Widget, TextSection

After playing around with our Column widget, let's clean up our code. We'll implement our own new StatelessWidget so we can re-use our code.

// location_detail/text_section.dart

import 'package:flutter/material.dart';

class TextSection extends StatelessWidget {
  final Color _color;

  TextSection(this._color);

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: _color,
      ),
      child: Text('hi'),
    );
  }
}

Finally, let's update our location_detail/location_detail.dart file to use our new widget:

// location_detail/location_detail.dart

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

class LocationDetail extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Hello'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextSection(Colors.red),
            TextSection(Colors.green),
            TextSection(Colors.blue),
          ],
        ));
  }
}

Lastly, let's update our app.dart file to:

// app.dart

import 'package:flutter/material.dart';
import 'screens/location_detail/location_detail.dart';

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LocationDetail(),
    );
  }
}

Summary

We can now implement the real content of our screen, now that our layout is defined.