Building basic layouts — Flutter Tutorial Part 2

Widgets, Layouts, Navigations. Join the community of millions of Flutterians! This time, we’re going to build the basic layouts for our Landscape Application!

In our last story, we talked about the benefits of using Flutter. It provides us lots of interesting and useful features. And in this story we are going to build the basic layouts of the detail page.

Let’s start by opening the project we created with Android Studio. After some processing time, the main window appears.

Flutter Generated Files

Now take a moment to review the most important files.

First, be sure the Project window is open (select View > Tool Windows > Project) and the Project view is selected from the drop-down list at the top of that window. You can then see the following files:

pubspec.yaml

This file is generated for every Dart project. It declares the metadata of the application. All of this metadata goes in the package’s pubspec: a file named pubspec.yaml that’s written in the YAML language.

We will talk about that later in another passage.

lib

This is actually the folders stores the dart files.

ios / android / macos…

This is actually the folders created for different platforms.

That’s all the files / folders we will use in this story. Let’s start coding.

This guide then takes a step back to explain Flutter’s approach to layout, and shows how to place a single widget on the screen. After a discussion of how to lay widgets out horizontally and vertically, some of the most common layout widgets are covered.

Creating application entry

Every program need an entry. We’ll now recreate the app base code from zero. Let’s begin with deleting the generated code in your main.dart file right now. The void main() identifies the app’s entry point.

void main() {
runApp(LandscapeApplication());
}

Calling runApp(Widget app) will inflate the given widget and attach it to the screen. Let’s create our application widget with StatelessWidget . Don’t worry if you are not able to understand the code. We will talk about that later:

class LandscapeApplication extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // Hide the debug banner
theme: ThemeData.light(),
color: Colors.white,
home: Scaffold(
appBar: AppBar(
title: Text('Landscape', style: TextStyle(color: Colors.black)),
backgroundColor: Colors.white,
centerTitle: false,
brightness: Brightness.light),
body: Row(children: [
Expanded(
child: Text("Landscape Application!",
style: TextStyle(fontSize: 20), textAlign: TextAlign.center))
]),
),
);
}
}

As what you see above in the code, in Flutter, we need to override Widget build(BuildContext context) to build a widget’s content. Here, we will use MaterialApp to build the application.

Now Simply save the file and click the run button in the main toolbar or use the hotkey Ctrl + r to run your application on your device you selected in the toolbar:

[iOS] Notice that you are supposed to edit the settings of the Runner.xcworkspace in macos & ios folders so that the compilers can sign your application automatically.

[Android] Notice that you will have to allow the adb to install the new application (if the system prompted a dialog asking for permission) on your phone for the first time.

After some compiling time, you will see your simulator / your physical device opening the application with the interface of an AppBar and a text showing “Landscape Application!”

Yet, it’s running perfectly on our iPhone Simulator, but it’s not enough. We will next create the detail page showing landscapes.

Combine Views Using Rows, Columns, Containers..

Beyond the widget you created in the previous section, you’ll create the the detail page to contain details about the landmark, such as the name of it and the country it is in.

In this section, you’ll use a Column to place the title above a Row that contains details about the park.

Create a new folder pagefirst which it will be used to store our model files later. Let’s create detailPage.dart inside that folder and open the file.

import 'package:flutter/material.dart';

class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: finish the widget
}
}

The method @override build(BuildContext context) is where the child widget of this widget contains, and we need to return the widget we want to build. For example we want to build a Text widget:

import 'package:flutter/material.dart';

class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text("Hello World");
}
}

Edit the code by hand to add a DetailPage class which extends StatelessWidget to create a new page.

Notice that as you change the code in a view’s body property, the Android Studio will automatically perform a hot-reload to apply your changes to your application.

Then, we can finish the widget by adding the code below to the class:

return Scaffold(
appBar: AppBar(
title: Text("Lake Tekapo", style: TextStyle(color: Colors.black)),
brightness: Brightness.light,
backgroundColor: Colors.white,
centerTitle: false),
body: DetailPageContent());

We use Scaffold as the container of the page. And the content of the page should now be the park’s name of the landscape and the country the landscape is in.

To lay widgets horizontally, we need to embed the widgets with Row .

To cause a child to expand to fill the available horizontal space, we need to wrap the child in an Expanded widget.

The Row widget does not scroll (and in general it is considered an error to have more children in a Row than will fit in the available room). If you have a line of widgets and want them to be able to scroll if there is insufficient room, consider using a ListView. For a vertical variant, see Column.

If you only have one child, then consider using Align or Center to position the child. So now, embed the text with a Row widget like this:

class DetailPageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(children: [
Text("Lake Tekapo Regional Park", style: TextStyle(fontSize: 14)),
Text("New Zealand", style: TextStyle(fontSize: 14))
]);
}
}

And before running the application, we need to change the property home of the MaterialApp like this:

class LandscapeApplication extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
color: Colors.white,
home: DetailPage()
);
}
}

Run the application.

Yes we make it!

But that’s not so beautiful.

To direct the layout to use the full width of the device, separate the park and the country by adding a Spacer to the Row holding the two text widgets.

A spacer expands to make its containing view use all of the space of its parent view, instead of having its size defined only by its contents.

And now, it should look like this:

But the it’s obviously too narrow between the edges and the widgets. How to solve the problem? Let’s give them some paddings with Padding widget:

...
return
Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(children: [
Text("Lake Tekapo Regional Park", style: TextStyle(fontSize: 14)),
Spacer(),
Text("New Zealand", style: TextStyle(fontSize: 14))
]));
...

Run the application again:

That’s amazing!

Create a Custom Image View

With the name and location widgets all set, the next step is to add an image for the landscape.

Instead of adding more code in this file, you’ll create a custom view that applies a mask, border, and drop shadow to the image.

Start by adding an image to the project’s assets folder.

Add assets to Flutter Assets

Add the asset by copying the image of the landscape to /assets:

Then open pubspec.yaml and add assets folder path to the configurations:

flutter:
uses-material-design: true
assets:
- assets/

Click Pub Get on the top of the yaml file in the Android Studio or run flutter pub get in the project.

Now we will use Container and BoxDecoration to draw the image as the background.

It should look like:

Container({BoxDecoration decoration, 
Widget child,
width = double.infinity,
height = double.infinity});

And Box Decoration should be like:

BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/LakeTekapo.JPG")))

Here we use BoxFit.cover to make the DecorationImage cover the whole Container. The BoxFit enum is to declare how a box should be inscribed into another box. For more BoxFit usages, visit BoxFit enum.

You are supposed to make the Container to have infinity width and height so that the DecorationImage can fill the whole screen.

So it should be:

return Container(
width: double.infinity,
height: double.infinity,
decoration: new BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/LakeTekapo.JPG"))),
child: Column(children: [
Container(
color: Colors.white,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(children: [
Text("Lake Tekapo Regional Park",
style: TextStyle(fontSize: 14)),
Spacer(),
Text("New Zealand", style: TextStyle(fontSize: 14))
]))),
Spacer(),
]));

Save and Run!

And you will see a perfect screen!

Hmm… but I would like to set the opacity of the image like the second one…

Let’s use ColorFilter :

decoration: new BoxDecoration(
image: DecorationImage(
colorFilter: new ColorFilter.mode(
Colors.white.withOpacity(0.8), BlendMode.dstATop),
fit: BoxFit.cover,
image: AssetImage("assets/LakeTekapo.JPG")),
)

It looks better~

You now have all of the components you need — the park and country, a background image, and a big title in the AppBar widget.

Conclusion

In this story, we successfully built our first application and created our detail page showing the beautiful scenery and the basic information of the landscape. We will then talk about lists and navigations in the next story.

Thanks for your reading~

Cheers ψ(`∇´)ψ~ 这里是苏苏的企鹅~