Flutter Tutorial 1:レイアウト構築をやるわよ

環境構築と「Hello World」が終わったから今度は公式が提供してるチュートリアルをやっていくわ。

今回から結構入力するコード量が増えるからそのつもりでね。


レイアウトの構築

最初のチュートリアルは Building layouts よ。画面レイアウトの基本的な構造を学ぶわ。

このチュートリアルで学べるのは

  • Flutterのレイアウトのメカニズム
  • ウィジェットの垂直、水平配置
  • 画像の表示
こんなところね。

まずは完成形の作成

先に必要なコードの入力、画像ファイルの設定を終わらせてどんな表示になるのかを確認するわよ。それからコードの中身を見ていくことにするわ。

main.dart の入力

新しい Flutterプロジェクトを作成して、main.dart に次のコードを入力してちょうだい。

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
  /* titleSectionの設定 */
    Widget titleSection = Container(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            /*1*/
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                /*2*/
                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],
                  ),
                ),
              ],
            ),
          ),
          /*3*/
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          const Text('41'),
        ],
      ),
    );

    Color color = Theme.of(context).primaryColor;
	
    /* buttonSectionの設定 */
    /* ここは同じレイアウトの配置が3つ並ぶので buildButtonColumn()メソッドとしてメソッド化 */
    /* 引数には色とアイコンの種類と表示テキストを指定 */
    Widget buttonSection = Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        _buildButtonColumn(color, Icons.call, 'CALL'),
        _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
        _buildButtonColumn(color, Icons.share, 'SHARE'),
      ],
    );
    
    /* textSectionの設定 */
    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,
      ),
    );

    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),
      ),
    );
  }

/* buildButtonColumn()メソッド */
  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,
            ),
          ),
        ),
      ],
    );
  }
}

お疲れ様。結構な量だったわね。でもまだ終わりじゃないわ。表示するための画像を用意しなきゃダメなの。

画像ファイルの準備

lanke.jpgの準備

ここに行って必要な画像を手に入れてちょうだい。main.dart の一つ上の階層に imagesフォルダを作って、その中に lake.jpg を入れておいて。

pubspec.yaml の修正

それから準備した画像をコード内で扱えるようにセットするわよ。
main.dart の一つ上のフォルダに pubspec.yaml ってファイルがあるからエディタで開いて、下のコードを見つけてちょうだい。

  flutter:
  	uses-material-design: true
  
見つかったらすぐ下にコードを追加してね。
>
    assets:
    	- images/lake.jpg
       
これで画像ファイルを参照できるようになったわ。

デモの実行

ここまで終わったら完成よ。実行してみて。



コードの中身を見る

コードの中身を詳しく見ていくわよ。

レイアウトの構成

まずはレイアウトの確認よ。キャンバスの上に大きな要素が縦に4つ並んでいることが分かるわね。


このうち、②③④のセクション名はそのままコード内に出てくるわ。②と③には子要素も設定されているわよ。

② titleSection と③ buttonSection の子要素の構成は次の通りよ。公式の画像が分かりやすくて良い感じ。参考にさせてもらってるわ。


それぞれの要素はこんな感じで入れ子状態よ。

titleSectionのレイアウト

実際のコードで titleSection の設定部分を見てみるわよ。
Widget titleSection = Container(
    padding: const EdgeInsets.all(32),
    child: Row(
		children: [
        	Expanded(
            /*1*/
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                /*2*/
                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],
                  ),
                ),
              ],
            ),
          ),
          /*3*/
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          const Text('41'),
        ],
	),
);
Container の paddingプロパティは Widget の内側の余白を指定するわ。EdgeInsets.all で titleSection 全体の上下左右の余白を32にセットしているわよ。

child: Row は行方向の子要素よ。列と行でたまに混乱するけれど、画面の上下方向を列(Column)、画面の左右方向を行(Row)、と考えれば良いわ。

コメントアウトしてある /*1*/のところの上の Expanded は列の横幅を引き延ばす指定よ。タイトルセクションの一番左端の列の子要素に Expanded を指定すると、残りの空きスペースを全部埋めるように引き延ばされるの。★と数字を表示してある要素を右端に寄せて揃えるために指定しているわ。

一番左の子要素にさらに上から順番に設定していくわ。
crossAxisAlignment は子要素のクロス(x軸=行とy軸=列)配置の指定よ。ここではあんまり気にしなくていいわ。Column(列要素)に対して指定してあるから要素が上下に配置されるのよ。

/*2*/のところで、テキスト要素を上下に2つ配置してそれぞれに「Oeschinen Lake Campground」と「Kandersteg, Switzerland」って文字を表示しているわ。TextStyle で太字にしたり文字色を変えたりしてね。

/*3*/のところで★アイコンを赤色で表示して、その横に「41」で固定値で表示しているわ。

buttonSectionのレイアウト

次は buttonSection のレイアウト設定の部分よ。ここはメソッド化されていて2ヶ所の分かれているわ。
まずは Widget に設定されている部分よ。
Widget buttonSection = Row(
	mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
    	_buildButtonColumn(color, Icons.call, 'CALL'),
        _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
        _buildButtonColumn(color, Icons.share, 'SHARE'),
	],
);
 
次は buildButtonColumn()メソッドの中身よ。
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 は横に3列、縦に2行の構成ね。
それぞれ1行目に「受話器マーク」「矢印」「shareマーク」のアイコンが並んでいて、その下に「CALL」「ROUTE」「SHARE」とテキストが表示されているわ。
実際にアプリではアイコンを押せば機能が立ち上がるけれど、今回はレイアウトのチュートリアルだからただのハリボテよ。

実際にこの配置を作るのにbuildButtonColumn()メソッドを作っているわ。
buildButtonColumn()メソッドではアイコンの表示色とアイコンデータとテキストの文字列を受け取ってアイコンとテキストを Column つまり列方向(上下)に配置しているの。
呼び出し元の buttonSection でアイコンの種類とテキストを変えて3回呼ばれているわ。

textSectionのレイアウト

最後のセクション textSection の設定よ。特に目新しいものは無いわね。
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,
      ),
 );

配置

最後に画像と各セクションを配置していくわ。
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter layout demo'),
        ),
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600,
              height: 240,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),
      ),
    );

body: ListView 以下が実際に配置している部分よ。
ListViewはスクロール可能な Widget リストよ。画像と titleSection、buttonSection、textSectionを入れているわ。



今日はここまでよ。
次はこれに修正を加えてアイコンをタップできるようにインタラクティブ機能を追加するわ。
あでぃおす。



コメント