FlutterのColumnとRowを使ってみた

FlutterでColumnとRowを使ってレイアウトを作ってみた。
2018.08.08

大阪オフィスの山田です。最近、プライベートでFlutterでごにょごにょするのにハマっています。今回、ColumnRowを使って、レイアウトを作ってみました。元ネタは公式のこちら。※色々試しながら実装した部分が多いので、参照先とはソースコードが異なります。

開発環境

flutter doctorの結果です。

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v0.5.1, on Mac OS X 10.13.5 17F77, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.1)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.3)
[✓] Android Studio (version 3.1)
✗ Flutter plugin not installed; this adds Flutter specific functionality.
✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.25.1)
[✓] Connected devices (1 available)

VS CodeのWarningが消えない...(´;ω;`)うっ...
(実際にはExtensionのインストールはうまくいっている)。

作るレイアウト

それではがんばっていきます。

最初にエリア分け

全体はカードになっています。そしてカードの中は以下のようにエリア分けができます。

それぞれのAreaは一列に縦に並んでいると考えて、Columnを使用します。なので、Widgetのbuildを以下のように書きます。

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Card Layout"),
      ),
      body: Card(
        elevation: 4.0,
        margin: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Image.asset('assets/neko1_600x400.jpg'),
            _titleArea(),
            _buttonArea(),
            _descriptionArea()
          ],
        ),
      )
    );
  }

body部分ですが、まず上下左右のマージンを16取ってCardを配置します。そのchildにColumnを用意して、各要素を縦に並べていきます。画像は本来はAspectFitのような処理を入れるべきですが、今回はassetを表示しているだけにしています。続いて、Title Area Button Area Description Areaを描画する処理にうつります。

Title Area

続いてTitle Areaですが、今回自分で実装してみてこの部分が一番難しかったです。Title Areaは以下のように分割することができます。

この構造を実現するのに以下の手順を行います。

  • 1行目(Row)を用意する
  • 1行の中には2.1、2.2、2.3があり、順番に用意していく。
  • 2.1をExpand(広げる)する
  • 2.1の中には縦にTextが2つ並ぶので列(Column)を用意する。
  • 3.1.1、3.1.2を用意する。

上記手順を実現したソースコードです。

  Widget _titleArea() {
    return Container(
      margin: EdgeInsets.all(16.0),
      child: Row(    // 1行目
        children: <Widget>[
          Expanded(  // 2.1列目
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Container(  // 3.1.1行目
                  margin: const EdgeInsets.only(bottom: 4.0),
                  child: Text(
                    "Neko is So cute.",
                    style: TextStyle(
                        fontWeight: FontWeight.bold, fontSize: 16.0),
                  ),
                ),
                Container(  // 3.1.2行目
                  child: Text(
                    "Osaka, Japan",
                    style: TextStyle(fontSize: 12.0, color: Colors.grey),
                  ),
                ),
              ],
            ),
          ),
          Icon(  // 2.2列目
            Icons.star,
            color: Colors.red,
          ),
          Text('41'),  // 2.3列目
        ],
      )
    );
  }

Button Area

Button部分は以下のように分解することが可能です。

Title Areaの時と同様に、手順化します。

  • 1行目(Row)を用意する
  • 1行の中には2.1、2.2、2.3があり、順番に用意していく。
  • 2の中は2行に分かれているため、Columnを用意して3.1.1,3.1.2を縦に並べる

上記手順を実現したソースコードです。

  Widget _buttonArea() {
    return Container(
        margin: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
        child: Row( // 1行目
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            _buildButtonColumn(Icons.call, "CALL"), // 2.1
            _buildButtonColumn(Icons.near_me, "ROUTE"), // 2.2
            _buildButtonColumn(Icons.share, "SHARE") // 2.3
          ],
        ));
  }

  Widget _buildButtonColumn(IconData icon, String label) {
    final color = Theme.of(context).primaryColor;
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(icon, color: color), // 3.1.1
        Container( // 3.1.2
          margin: const EdgeInsets.only(top: 8.0),
          child: Text(
            label,
            style: TextStyle(
                fontSize: 12.0,
                fontWeight: FontWeight.w400,
                color: color),
          ),
        )
      ],
    );
  }

1行目をRowで用意して、各ボタンのColumnを別メソッド_buildButtonColumnで構築する流れになります。MainAxisAlignment.spaceEvenlyで、各要素間のスペースを均等にしています。アイコンとタイトルの間は、タイトルをContainerでラップしてマージンを取っています。

Description Area

Description AreaはExpandedで最大限領域を確保して、ContainerでラップしたTextを配置しているだけです。

  Widget _descriptionArea() {
    return Expanded(
      child: Container(
        margin: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
        child: Text('''
The Neko is very cute. The Neko is super cute. Neko has been sleeping during the day time. She gets up in the evening. she is playing around at night. She nestles up to me when I get a snack. She gets angly when I pick herup. She cries out like end of the world when I take her to the bathroom. When I am asleep she goes on. Therefore, sometimes, I can be apologized.
          '''),
      ),
    );
  }

以上となります。

最後に

まだまだ思い通りに動かせず、サンプルや公式のマニュアルを見ながら「こーやったらどうなるんだろ?」を繰り返して試行錯誤している段階です。今後もちょっとずつ学習を進めようかと思いますのでよろしくお願いします。