2020/10/20 Flutter : Layouts in Flutterの訳

このページのポイント

  • ウィジェットはUIを構築するためのクラスです。
  • ウィジェットは「UIの要素」と「レイアウト」の両方のために使用されます。
  • シンプルなウィジェットを組み合わせて複雑なウィジェットを構築します。

Flutterのレイアウトメカニズムの中心はウィジェットです。Flutterではほとんど全てのものがウィジェットです。モデルのレイアウトもウィジェットです。

画像、アイコン、テキスト、Flutterアプリで見るこれらのものは全てウィジェットです。

そして目に見えないウィジェットもあります。rowウィジェット,columnウィジェット,gridウィジェットなどです。これらは目に見えるウィジェットをアレンジしたり、制限したり、配置したりする役割を持つウィジェットです。

より複雑なウィジェットを構築するために、ウィジェットを組み合わせてレイアウトを作ります。例えば下のスクリーンショットは3つのアイコンのそれぞれの下部にラベルが追加されたレイアウトです。

図1

Sample layout


このレイアウトを目に見える構成を示したのが下のスクリーンショットです。

Rowが3つのcolumnを含んでいます。そしてそれぞれのcolumnがアイコンとラベルを含んでいます。

図2

Sample layout with visual debugging


図1のウィジェットツリーダイアグラムを以下に示します。

図3

Node tree

ほとんどが期待通りだと思いますが、Containerウィジェットについて疑問を持つ方もおられるでしょう。

Containerウィジェットは、子ウィジェットをカスタマイズするためのウィジェットクラスです。子ウィジェットに対して、パディング、マージン、ボーター、背景色、などを追加したい時、また、その機能の一部に名前を付ける時に使用します。

図1では、それぞれのTextウィジェットは、マージンを追加するためにContainerウィジェットでラップされています。

Rowウィジェットも、周りにパディングを追加するためにContainerでラップされています。

このサンプルのそれ以外のUIはプロパティを指定してコントロールします。

Iconウィジェットのcolorプロパティを指定することで色を決めます。

Textウィジェットのstyleプロパティを指定することで、フォント、色、太さ、などを決定します。

RowやColumnには、

「子ウィジェットを水平方向、垂直方向にどのように配置するか」

「子ウィジェットがどのようにスペースを占有するか」

を指定するプロパティが用意されています。


Lay out a widget

Flutterでは一つのウィジェットをどのようにレイアウトしますか?このセクションではシンプルなウィジェットを生成して表示する方法を見ていきます。シンプルなHello World appのコード全体を見ていきます。

Flutterでは少しのステップでテキスト、アイコン、画像の表示が可能です。

1.Select a layout widget

これらの特性は通常、含まれているウィジェットに渡されるため、表示されるウィジェットをどのように配置または制約するかに基づいて、さまざまなレイアウトウィジェットから選択します。

このサンプルでは水平方向、垂直方向に中央よせするためにCenterウィジェットが使用されています。

2. Create a visible widget

たとえば、Textウィジェットを使います↓。

Text('Hello World'),

Imageウィジェットを生成します。

Image.asset(
  'images/lake.jpg',
  fit: BoxFit.cover,
),

Iconウィジェットを生成します。

Icon(
  Icons.star,
  color: Colors.red[500],
),

3. Add the visible widget to the layout widget

全てのレイアウトウィジェットは以下のいずれかです。

一つの子ウィジェットを持つので、childプロパティを持っている(Center,Containerなど)

複数のウィジェットのリストを持つのでchildrenプロパティを持っている(Row,Column,ListVIew,Stackなど)

下記のサンプルはTextウィジェットをCenterウィジェットでラップしています。

Center(
  child: Text('Hello World'),
),

4. Add the layout widget to the page

Flutterアプリはそれ自体がwidgetです。そしてほとんどのwidgetはbuild()メソッドを持っています。アプリのbuild()メソッド内でウィジェットをインスタンス化、してreturnすることでそのウィジェットが表示されます。

Material apps

MaterialAppウィジェットの中でScaffoldウィジェットを使うことができます。Scaffoldウィジェットはデフォルトのバナー(banner)、背景色、ドロワー・スナックバー・ボトムシートを追加するためのAPIを持っています。

そしてホームページのbodyプロパティに直接Centerウィジェットを配置しています。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

注:マテリアルライブラリは、マテリアルデザインの原則に従うウィジェットを実装しています。 UIを設計するときは、標準のウィジェットライブラリのウィジェットのみを使用することも、マテリアルライブラリのウィジェットを使用することもできます。 両方のライブラリのウィジェットを混在させたり、既存のウィジェットをカスタマイズしたり、独自のカスタムウィジェットのセットを作成したりできます。


Non-Material apps

non-material appは、appのbuild()メソッドにCenterウィジェットを追加できます。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(color: Colors.white),
      child: Center(
        child: Text(
          'Hello World',
          textDirection: TextDirection.ltr,
          style: TextStyle(
            fontSize: 32,
            color: Colors.black87,
          ),
        ),
      ),
    );
  }
}

デフォルトで、non-MaterialアプリはAppBar,title,backgroundColorを含みません。non-Materialアプリでこれらの機能を実装したければ自分で構築しないといけません。

ソースコード(Material app)

ソースコード(Non-Material app)


Lay out multiple widgets vertically and horizontally

最も一般的なレイアウトパターンは、ウィジェットを水平方向、あるいは垂直方向に並べるものです。

ウィジェットを水平方向に並べるためのウィジェットからRowウィジェットです。

ウィジェットを垂直方向に並べるためのウィジェットからColumnウィジェットです。

このセクションのポイント

  • RowとColumnは、最も一般的に使用される2つのレイアウトパターンです。
  • RowとColumnはそれぞれ子ウィジェットのリストを取ります。
  • 子ウィジェット自体は、Row、Column、またはその他の複雑なウィジェットにすることができます。
  • RowまたはColumnが、その子を垂直方向と水平方向の両方に配置する方法を指定できます。
  • 特定の子ウィジェットを拡大または制約できます。
  • 子ウィジェットがRowまたはColumnの使用可能なスペースをどのように使用するかを指定できます。

FlutterでRow,Columnを生成するには、子ウィジェットを要素にもつList(List<Widget>)を、Row,Columnのchildrenプロパティに設定する必要があります。

Row,Columnの子ウィジェットをRow,Columnウィジェットとすることもできます。

下記のサンプルではRow,Columnのネストがどのようにできるのかを示しています。

Screenshot with callouts showing the row containing two children

上記のレイアウトはRowが二つのウィジェットを持っています。

左側がColumnで、右側がImageです。

Diagram showing a left column broken down to its sub-rows and sub-columns

左側のColumnは子孫ウィジェットとしてRowやColumnを保持しています。

Note:RowやColumnをカスタマイズしてウィジェットを構築することができますが、Flutterではより高レベルなウィジェットが用意してあり、あなたのニーズによってはそちらを使用した方が便利な場合も多いです。

例えばRowの代わりにListTileウィジェットを使うことができます。ListTileウィジェットは前と後ろのウィジェット、さらに3行のテキストを指定できる便利なウィジェットです。

Columnの代わりにListViewウィジェットを使った方が良い場合も多いです。子ウィジェットの数が多い場合自動でスクロールに対応してくれます。


Aligning widgets

rowやcolumnの子ウィジェットをどのように配置するかを、mainAxisAlignmentやcrossAxisAlignmentプロパティを使って指定します。

Rowにとってmain Axisは水平(横)方向、cross axisは垂直(縦)方向です。

Columnにとってmain axisは垂直(縦)方向、cross axisは水平(横)方向です。

Diagram showing the main axis and cross axis for a rowDiagram showing the main axis and cross axis for a column

MainAxisAlignmentクラスとCrossAxisAlignmentクラスにいくつもの定数が用意されているので、それをmainAxisAlignment、crossAxisAlignmentプロパティに設定することで配置方法を指定します。

Note:プロジェクトのimagesディレクトリに画像を追加し、pubspec.yamlファイルに、画像へのアクセスを宣言する必要があります。このサンプルではImage.assetメソットが使われています。

Image.networkメソッドを使用してオンライン上の画像を表示する場合これは必要ありません。

下のサンプルでは、三つの画像それぞれ100pxの幅です。そしてレンダーボックス(このケースでは全画面)の幅は300px以上あります。したがってmainAxisAlignmentプロパティをMainAxisAlignment.spaceEvenlyに指定した場合、「右の画像の右側」、「それぞれの画像の間」、「左の画像の左側」により余りのスペースが等分されます。

GitHubのプロジェクトを自分のパソコンにダウンロードして、サンプルプロジェクトを実行する方法

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset('images/pic1.jpg'),
    Image.asset('images/pic2.jpg'),
    Image.asset('images/pic3.jpg'),
  ],
);

Row with 3 evenly spaced images

ColumnはRowと同じように機能します。 次の例は、それぞれが100ピクセルの高さの3つの画像の列を示しています。 レンダーボックスの高さ(この場合は画面全体)は300ピクセルを超えるため、mainAxisAlignmentプロパティをspaceEvenlyに設定すると、各画像の間、上、下の空き垂直スペースが均等に分割されます。

Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset('images/pic1.jpg'),
    Image.asset('images/pic2.jpg'),
    Image.asset('images/pic3.jpg'),
  ],
);

Sizing widgets

レイアウトが大きすぎてデバイスの画面をはみ出してしまう場合、黄色と黒のストライプが側面に表示されます。

下のサンプルは幅が広すぎるRowの例です。

Overly-wide row

ウィジェットは、Expandedウィジェットを使用して、RowまたはColumnに収まるようにサイズを変更できます。

画像の行がレンダリングボックスに対して広すぎる前の例を修正するには、各画像を展開ウィジェットでラップします。

GitHubのプロジェクトを自分のパソコンにダウンロードして、サンプルプロジェクトを実行する方法


Nesting rows and columns

Flutterのレイアウトでは、Row,Columnの子孫ウィジェットとして必要な深さでRow,Columnをネストさせることができます。

Screenshot of the pavlova app, with the ratings and icon rows outlined in red

上記の

「レーティング(星五つ)とレビュー数」の部分が「レーティングRow」、

その下の三つのアイコンがある部分が「アイコンRow」

「レーティングRow」、「アイコンRow」を合わせた部分(赤線で囲まれた部分)

がoutlined section。

outlined sectionは二つのRowで実装されています。「レーティングRow」は五つの星とレビュー数で構成されます。

「アイコンRow」はアイコンとテキストを含むColumnを三つ有しています。

「レーティングRow」のウィジェットツリーを下記に示します。

Ratings row widget tree

ratings変数に、「小さい⭐️アイコン五つを含むRow」と「テキスト」を含むRowをセットします。

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;

void main() {
  debugPaintSizeEnabled = true; // Remove to suppress visual layout
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    Widget stars=Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
      ],
    );

    Widget ratings = Container(
      padding: EdgeInsets.all(20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          stars,
          Text(
            '130 Reviews',
            style: TextStyle(
              color: Colors.black,
              fontWeight: FontWeight.w800,
              fontFamily: 'Roboto',
              letterSpacing: 0.5,
              fontSize: 20,
            ),
          ),
        ],
      ),
    );



    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        // Change to buildColumn() for the other column example
        body: ratings,
      ),
    );
  }
}

 

Tip:ネストが深くなると視覚的に混乱が生じる(コードが見辛い)ので、上記のように変数を使い、あるいは関数を宣言したりして、ネストが深くなるのを防止しましょう。


「アイコンRow」「レーティングRow」の下部に配置されており、3つのcolumnを持っています。それぞれのcolumnがアイコンと2行のテキストを持っており、ウィジェットツリー(Widget tree)は下記のようになります。

Icon widget tree

iconList変数に「アイコンRow」を構成するウィジェットをセットします。

import 'package:flutter/material.dart';
//import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;

void main() {
  //debugPaintSizeEnabled = true; // Remove to suppress visual layout
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    Widget stars=Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
      ],
    );

    Widget ratings = Container(
      padding: EdgeInsets.all(20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          stars,
          Text(
            '130 Reviews',
            style: TextStyle(
              color: Colors.black,
              fontWeight: FontWeight.w800,
              fontFamily: 'Roboto',
              letterSpacing: 0.5,
              fontSize: 20,
            ),
          ),
        ],
      ),
    );

    final descTextStyle = TextStyle(
      color: Colors.black,
      fontWeight: FontWeight.w800,
      fontFamily: 'Roboto',
      letterSpacing: 0.5,
      fontSize: 18,
      height: 2,
    );

// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
    final iconList = DefaultTextStyle.merge(
      style: descTextStyle,
      child: Container(
        color:Colors.yellow,
        padding: EdgeInsets.all(20),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Column(
              children: [
                Icon(Icons.kitchen, color: Colors.pink[500]),
                Text('PREP:'),
                Text('25 min'),
              ],
            ),
            Column(
              children: [
                Icon(Icons.timer, color: Colors.pink[500]),
                Text('COOK:'),
                Text('1 hr'),
              ],
            ),
            Column(
              children: [
                Icon(Icons.restaurant, color: Colors.pink[500]),
                Text('FEEDS:'),
                Text('4-6'),
              ],
            ),
          ],
        ),
      ),
    );

    final leftColumn = Container(
      //color:Colors.purple,
      padding: EdgeInsets.fromLTRB(20, 30, 20, 20),
      child: Column(
        children: [
          /*titleText,  まだ作ってないのでコメントアウト */
          /*subTitle,  まだ作ってないのでコメントアウト */
          ratings,
          iconList,
        ],
      ),
    );


    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        // Change to buildColumn() for the other column example
        body: leftColumn,
      ),
    );
  }
}


leftColumn変数に、ratings変数(「レーティングRow」)とiconList変数(「アイコンRow」)を子ウィジェットとして持つColumn(をContainerでラップしたもの)をセットします。


final leftColumn = Container(
  padding: EdgeInsets.fromLTRB(20, 30, 20, 20),  //←パディングを追加している
  child: Column(
    children: [
      titleText,
      subTitle,
      ratings,
      iconList,
    ],
  ),
);

上記コードでColumnをContainerでラップしているのは、Columnにパディングを追加するためです。

試しにpaddingの行をコメントアウトしてホットリロードすると、

パディングが無くなり、leftColumnが画面全体に広がります。


https://github.com/flutter/website

上記ページからダウンロードしたwebsiteディレクトリの

website/examples/layout/pavlova/images

にあるpavlova.jpgファイルを自分のプロジェクトのimagesディレクトリに移動(追加)します。

画像ファイルをimagesディレクトリにドラッグアンドドロップして追加する。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

pubspec.yamlファイルを開き、flutterセクションに下記のように、画像を読み込むためのコードを追加します。

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
  assets:                  //←ここを追加
    - images/            //←ここを追加

上記のように指定すれば、imagesディレクトリ内の全てのファイルを読み込めます。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

//main.dart

import 'package:flutter/material.dart';
//import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;

void main() {
  //debugPaintSizeEnabled = true; // Remove to suppress visual layout
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    Widget stars=Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.green[500]),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
        Icon(Icons.star, color: Colors.black),
      ],
    );

    Widget ratings = Container(
      padding: EdgeInsets.all(20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          stars,
          Text(
            '130 Reviews',
            style: TextStyle(
              color: Colors.black,
              fontWeight: FontWeight.w800,
              fontFamily: 'Roboto',
              letterSpacing: 0.5,
              fontSize: 20,
            ),
          ),
        ],
      ),
    );

    final descTextStyle = TextStyle(
      color: Colors.black,
      fontWeight: FontWeight.w800,
      fontFamily: 'Roboto',
      letterSpacing: 0.5,
      fontSize: 18,
      height: 2,
    );

// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
    final iconList = DefaultTextStyle.merge(
      style: descTextStyle,
      child: Container(
        color:Colors.yellow,
        padding: EdgeInsets.all(20),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Column(
              children: [
                Icon(Icons.kitchen, color: Colors.pink[500]),
                Text('PREP:'),
                Text('25 min'),
              ],
            ),
            Column(
              children: [
                Icon(Icons.timer, color: Colors.pink[500]),
                Text('COOK:'),
                Text('1 hr'),
              ],
            ),
            Column(
              children: [
                Icon(Icons.restaurant, color: Colors.pink[500]),
                Text('FEEDS:'),
                Text('4-6'),
              ],
            ),
          ],
        ),
      ),
    );

    final leftColumn = Container(
      //color:Colors.purple,
      //padding: EdgeInsets.fromLTRB(20, 30, 20, 20),
      child: Column(
        children: [
          /*titleText,  まだ作ってないのでコメントアウト */
          /*subTitle,  まだ作ってないのでコメントアウト */
          ratings,
          iconList,
        ],
      ),
    );

    final mainImage = Image.asset(
      'images/pavlova.jpg',
      fit: BoxFit.cover,
    );


    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        // Change to buildColumn() for the other column example
        body: Center(
          child: Container(
            margin: EdgeInsets.fromLTRB(20, 40, 20, 30),
            height: 600,
            child: Card(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    //width: 440,
                    child: leftColumn,
                  ),
                  mainImage,
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

上記コードをmain.dartファイルにコピペして実行します。

↓result

 

 

 

参考

https://flutter.dev/docs/development/ui/layout

コメントを残す

メールアドレスが公開されることはありません。