ロボット研究開発、ソフトウェア開発、コンテンツ制作配信

【Flutter】Widget(ウィジット)の階層構造について


FlutterのUIは、Widget(ウィジット)というパーツの階層構造になっています。
このWidgetが沢山種類があり、しかも単に表示するだけで「状態を持たないStatelessWidget」と「状態を持つStatefulWidget」に分類されます。
ウィジットについてブログにまとめることで頭の中を整理していきたいと思います。

スポンサーリンク

Widgetの種類

以下にウィジットの一部を表にまとめました。

役割 ウィジット 説明
画面全体の管理 MaterialApp アプリ全体を管理するルートウィジット
主なプロパティ: home, themeなど
Scaffold アプリの骨組みを構成するウィジット
主なプロパティ:appBar, bodyなど
AppBar 画面の上部に配置されるヘッダー部分(タイトルなど)
ウィジットの配置 Center 画面の中央に配置するためのウィジット
Column 縦に並べるためのウィジット
Row 横に並べるためのウィジット
Container 背景色、余白、サイズ、装飾などを設定できる汎用的なウィジェット
コンテンツの表示 Text 文字列を表示するウィジェット
Image 画像を表示するウィジット
Icon アイコンを表示するウィジット

とりあえず、ウィジットの役割から分類すると、画面全体の管理をするMaterialApp,Scaffold,AppBar、 ウィジットの配置などを行うCenter,Column,Row,Container、 コンテンツ表示するText,Image,Icon
などに分類できそうです。(他にもウィジットの種類はたくさんあると思いますが。)

Widgetの階層構造

実際に実装しながら、ウィジットの階層構造を理解してみます。
Dartは、Web上で簡単に実装が試せるDartPadというサイトがあります。簡単な実装の確認なら、DartPadで気軽に試せるので便利です。
DartPad

まずは、以下の様に実装してウィジットの階層の流れを把握します。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('test')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    width: 100,
                    height: 100,
                    color: Colors.blue,
                    child: Text('box1', style: TextStyle(color: Colors.white)),
                  ),
                  Container(
                    width: 100,
                    height: 100,
                    color: Colors.green,
                    child: Text('box2', style: TextStyle(color: Colors.white)),
                  ),
                ],
              ),
              Container(
                width: 100,
                height: 100,
                color: Colors.red,
                child: Text('box3', style: TextStyle(color: Colors.white)),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

・runAppは、設定されたウィジットを画面に適用する関数です。(4行目)
アプリのルートウィジェットとなるMaterialAppを呼び出し、引数のhomeにScaffoldを指定します。(5〜6行目)
Scaffoldはアプリの骨組みで、appBarやbodyのプロパティを持ちます。
appBarには、AppBarウィジットでタイトルをtestにしました。(7行目)
bodyプロパティには、Centerウィジットを設定しました。
Center内に、Columnウィジットを設定し、RowウィジットとContainerウィジットを縦に並べます。
Rowウィジット内では、2つのContainerを横に並べました。

実行結果と各ウィジットの階層を以下図にまとめます。↓

StatelessWidgetとStatefulWidget

Widgetには、単に表示するだけのStatelessWidgetと状態を持って、自分自身で表示を更新できるStatefulWidgetがあります。状態というとなんだか分かりにくいですが、何かしらの操作やイベントで変化するデータの事だと理解してます。

StatelessWidgetとStatefulWidgetの特徴を以下にまとめます。

◯StatelessWidget
・状態を持たず単に決まったことを表示するだけ
・buildメソッドを実装して一つ以上のwidgetを構成する
◯StatefulWieget
・状態を持つ。Stateクラスを実装する
・buildメソッドはStateクラスで実装する
・状態を変化させるときは、setState内で実施。
・setStateをコールすると、自分自身で表示更新する(buildメソッドが再び呼ばれる)

StatelessWidgetを実装する

それでは、StatelessWidgetを実装してみます。
さっき作ったプログラムの箱を表示する部分をStatelessWidgetとしてクラス化して作ってみます。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('test')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  BoxView(
                    text: 'box1_1',
                    color: Colors.green,
                  ),
//                   Container(
//                     width: 100,
//                     height: 100,
//                     color: Colors.blue,
//                     child: Text('box1', style: TextStyle(color: Colors.white)),
//                   ),
                  
                  BoxView(
                    text: 'box2_1',
                    color: Colors.blue,
                  ),
                  
//                   Container(
//                     width: 100,
//                     height: 100,
//                     color: Colors.green,
//                     child: Text('box2', style: TextStyle(color: Colors.white)),
//                   ),
                ],
              ),
              BoxView(
                text: 'box3_1',
                color: Colors.pink,
              )
//               Container(
//                 width: 100,
//                 height: 100,
//                 color: Colors.red,
//                 child: Text('box3', style: TextStyle(color: Colors.white)),
//               ),
            ],
          ),
        ),
      ),
    ),
  );
}


class BoxView extends StatelessWidget {
  const BoxView({super.key, required this.text, required this.color});

  final String text;
  final Color color;
  
  @override
  Widget build(BuildContext context) {
    return Container(
      width:100,
      height:100,
      color: color,
      child: Text(text, style: TextStyle(color: Colors.white)),
    );
  }
}

58-73行目で、StatelessWidgetを継承したBoxViewというクラスを作りました。
これは、textとcolorを設定してインスタンスが作られると、buildメソッドが呼ばれ、設定したtextとcolorでContainerウィジットを作成するクラスです。
状態を持たず、シンプルに指定されたものを表示するだけです。
これをrunApp内の各ウィジットから呼びます。(16〜18行目、26〜29行目、39〜42行目)
textとcolorは最初の実装から変えてみました。

実行するとアプリ画面は以下のようになります。

StatefulWidgetを実装する

一番分かりやすくてよく例にでてくる定番のカウンター画面を作ってみます。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('test')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  BoxView(
                    text: 'box1_1',
                    color: Colors.green,
                  ),
//                   Container(
//                     width: 100,
//                     height: 100,
//                     color: Colors.blue,
//                     child: Text('box1', style: TextStyle(color: Colors.white)),
//                   ),
                  
                  BoxView(
                    text: 'box2_1',
                    color: Colors.blue,
                  ),
                  
//                   Container(
//                     width: 100,
//                     height: 100,
//                     color: Colors.green,
//                     child: Text('box2', style: TextStyle(color: Colors.white)),
//                   ),
                ],
              ),
              BoxView(
                text: 'box3_1',
                color: Colors.pink,
              )
//               Container(
//                 width: 100,
//                 height: 100,
//                 color: Colors.red,
//                 child: Text('box3', style: TextStyle(color: Colors.white)),
//               ),
              ,
              CntBoxView()
            ],
          ),
        ),
      ),
    ),
  );
}


class BoxView extends StatelessWidget {
  const BoxView({super.key, required this.text, required this.color});

  final String text;
  final Color color;
  
  @override
  Widget build(BuildContext context) {
    return Container(
      width:100,
      height:100,
      color: color,
      child: Text(text, style: TextStyle(color: Colors.white)),
    );
  }
}


class CntBoxView extends StatefulWidget {
 
  const CntBoxView({super.key});
  
  @override
  State<CntBoxView> createState() => _CntBoxView();
}
  
class _CntBoxView extends State<CntBoxView> {
  
  int _cnt = 0;
  
  @override
  Widget build(BuildContext context){
    return GestureDetector(
      onTap: () {
        print('tap!!');
        setState(() {
          _cnt++;
        });
      },
      child: Container(
        width:100,
        height:100,
        color: Colors.brown,
        child: Text('$_cnt', style: TextStyle(color: Colors.white)),
      )
    );   
  }
}

CntBoxViewというStatefulWidgetを作成します。(78行目〜)
この中でcreateStateをオーバーライドし、Stateクラスを返します。(83行目)
新しく_CntBoxViewというStateクラスを作成します。(86行目〜)
この中で、buildメソッドを実装してウィジェットを配置します。
ボックスのタップを検知するため、ContainerウィジェットをGestureDetectorというもので括ります。(92行目)
Containerでは、ボックスを作り、_cnt変数を表示させます。(99行目〜104行目)
また、onTap()時に、setState()を呼び出し、引数内で、カウントアップします。(96行目)
setStateが呼び出されると、buildメソッドが再び呼ばれ、UIが再描画される仕組みになっています。
作ったCntBoxViewをmainの中からコールします。(50行目)

さて、これで実行してみます。以下の様なアプリ画面になります。
茶色のボックスをタップするとカウントアップされることが確認できました。

最後に

ということで、今回はWidgetについて整理してみました。
何度もWidgetを実装していると少しずつ理解できるようになってきました。
やっぱり初めての言語を理解していくには、手を動かして実装するのが一番ですね。
それでは!!

関連記事(一部広告含む)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA


ページトップボタン