Flutterチュートリアル:レイアウトの構築
FlutterFlutterの公式サイトは、とても丁寧で素晴らしい!けど、日本語はサポートされていない。ということで、公式サイトのマニュアルを日本語でまとめてみます。
公式サイトには、以下のレイアウト作成のチュートリアルがあります。
このチュートリアルで学べることは、以下の3つです。
- レイアウトがどうやって動いているか
- Widget を配置する方法
- レイアウトの構築方法
このチュートリアルを終えると、以下のようなアプリケーションのレイアウトが完成します。
では、さっそくチュートリアルを開始しましょう。
もくじ
Step.0 空のFlutterプロジェクトを作成する
最初のステップでは、以下の手順で新規にFlutterプロジェクトを作成します。
- VS Code で Cmd+Shift+P を押して「コマンドパレット」を開く
- 「Flutter: New Project」を入力して選ぶ
- 「Application」を選ぶ
- 「flutter_tutorial」という名前でプロジェクトを作成する
プロジェクトを作成したら、F5を押して実行します。すると、以下のようなデフォルトのアプリが起動します。
プロジェクトの作成と実行方法の詳細は、以下の記事が参考になります。
デフォルトのプロジェクトの main.dart は、余分な実装が含まれているので、中身を全部消します。以下のようにコードを変更します。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
コードがすっきりしましたね。実行すると、以下のようになります。
ここで、MatrialApp の home部分を理解してみましょう。ここの部分が実際にレイアウトを構築している箇所になります。
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
body: const Center(
child: Text('Hello World'),
),
),
まず、Scaffold というウィジェットがあります。これは、レイアウトの土台に当たるウィジェットです。土台がないとレイアウトを配置できないので Scaffold が必要なんだ、ということを理解できていれば十分でしょう。
appBar が画面上の青色のタイトルバー部分です。title に Textウジェットを指定して、タイトル名を表示させています。
body がレイアウトのメイン部分になります。Centerウィジェットで画面中央に寄せるようにして、その中に Textウィジェットを置いています。これによって、画面中央に Hello World という文字が表示されています。
Step.1 レイアウトの要素を構造化する
さて、さっそくレイアウトを実装していきたいのですが、まずFlutterにおけるレイアウトの構造を理解する必要があります。このステップでは、ColumnとRowをつかって、全体の構成を考えます。
まず大きい要素で構造化します。このチュートリアルでは、画像・タイトル・ボタン・テキストの4つのColumn の要素があります。
さらに、それぞれRowの要素を詳しくみてみます。「タイトル」の要素は、3つのRowと2つのColumnが組み合わさっています。
同じく「ボタンRow」の要素は、3つのColumnと2つのRowが組み合わさっています。
このように構造化しておくと、実装が楽になります。まとめると、以下のような全体構造になります。
それでは、次のステップからこの構造をプログラムで記述していきます。
Step.2 タイトル行の実装
Step.2 まず、タイトル行の左側の要素を実装します。MyApp クラスの build 関数の上に、以下のコードを書きます。
// タイトル部分のウィジェット
Widget titleSection = Container(
padding: const EdgeInsets.all(32), // 32pxのパディング
child: Row(
children: [
// Expandedは、空白部分に要素をめいいっぱい広げる
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Containerはパディングを設定するために使う
Container(
padding: const EdgeInsets.only(bottom: 8),
child: const Text(
'Oeschinen Lake Campground',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Text(
'Kandersteg, Switzerland',
style: TextStyle(
color: Colors.grey[500],
),
),
],
),
),
// アイコン
Icon(
Icons.star,
color: Colors.red[500],
),
const Text('41'),
],
),
);
この titleSection のウィジェットを以下のように MaterialApp の body に指定します。
return MaterialApp(
title: 'Flutter layout tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
// titleSection を指定する
body: Column(
children: [titleSection],
),
),
);
これで、タイトル行の部分が表示されるようになります。
Step.3 ボタン部分の実装
ボタンの部分は、アイコンとテキストで構成される要素が3つ並んでいます。この3つの要素は、等間隔で横並びになってます。
同じ要素が3つあるので、3つ同じ実装をしても良いのですが、処理を共通化させるために MyApp クラス内に _buildButtonColumn という関数を用意します。
// ボタンのカラムを作成する関数(アンダーバーではじめると private関数になる)
// color : ボタンの色
// icon : アイコンの種類
// label : ボタンのテキスト
Column _buildButtonColumn(Color color, IconData icon, String label) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
タイトル部分と同じように、buttonSection を build関数内に用意します。
// primary色を取得する
Color color = Theme.of(context).primaryColor;
// ボタンカラムを3つ横並びで用意する
Widget buttonSection = Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButtonColumn(color, Icons.call, 'CALL'),
_buildButtonColumn(color, Icons.near_me, 'ROUTE'),
_buildButtonColumn(color, Icons.share, 'SHARE'),
],
);
最後に、MaterialApp の body に buttonSection を指定します。
return MaterialApp(
title: 'Flutter layout tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
body: Column(
children: [titleSection, buttonSection],
),
),
);
これで、ボタン部分が実装完了です。
Step.4 テキスト部分の実装
テキスト部分も buttonSection と同様に入れます。上下左右に空白を入れたいので、Paddingウィジェットを用います。また、テキストの折り返す場所を単語の区切り目にするために、softWrap オプションを true に設定します。
Widget textSection = const Padding(
padding: EdgeInsets.all(32),
child: Text(
'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
'Alps. Situated 1,578 meters above sea level, it is one of the '
'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
'half-hour walk through pastures and pine forest, leads you to the '
'lake, which warms to 20 degrees Celsius in the summer. Activities '
'enjoyed here include rowing, and riding the summer toboggan run.',
softWrap: true,
),
);
MaterialApp の body に textSection を指定します。
return MaterialApp(
title: 'Flutter layout tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
body: Column(
children: [titleSection, buttonSection, textSection],
),
),
);
これでテキスト部分の実装が完了です。
Step.5 画像部分の実装
まず、画像ファイルをプロジェクトフォルダに追加します。わかりやすいように image フォルダを作成して、その中に 画像ファイルを入れます。
あとは、プログラムから呼び出すだけ。としたいのですが、Flutterではアセットとして登録する必要があります。じゃっかん面倒ですね。以下のように pubspec.yaml ファイルを編集して、assets に先ほど追加した lake.jpg ファイルを指定します。このときに、yamlファイルではスペースが特別な意味を持つので、スペースも含めて設定するようにします。
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- images/lake.jpg
最後に、dart プログラム側から呼び出します。これまでと同じように imageSection というWidgetを用意して、MaterialApp の body に追加します。
Widget imageSection = Image.asset(
"images/lake.jpg",
width: 600,
height: 240,
fit: BoxFit.cover,
);
return MaterialApp(
title: 'Flutter layout tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
body: Column(
children: [imageSection, titleSection, buttonSection, textSection],
),
),
);
これで完成です。
Step.6 最後の調整
このままで完成としても良いのですが、テキストが長くなったときに現状の実装ではスクロールできません。スクロールできるように修正しておきましょう。スクロールさせるには body の Column ウィジェットではなくて、ListView ウィジェットを使うようにします。
return MaterialApp(
title: 'Flutter layout tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout tutorial'),
),
body: ListView(
children: [imageSection, titleSection, buttonSection, textSection],
),
),
);
スクロール操作が効くようになりました。
まとめ
ああ