【Flutter】Widget(ウィジット)の階層構造について
FlutterのUIは、Widget(ウィジット)というパーツの階層構造になっています。
このWidgetが沢山種類があり、しかも単に表示するだけで「状態を持たないStatelessWidget」と「状態を持つStatefulWidget」に分類されます。
ウィジットについてブログにまとめることで頭の中を整理していきたいと思います。
Widgetの種類
以下にウィジットの一部を表にまとめました。
役割 | ウィジット | 説明 |
---|---|---|
画面全体の管理 | MaterialApp | アプリ全体を管理するルートウィジット主なプロパティ: home, themeなど |
Scaffold | アプリの骨組みを構成するウィジット主なプロパティ:appBar, bodyなど | |
AppBar | 画面の上部に配置されるヘッダー部分(タイトルなど) | |
ウィジットの配置 | Center | 画面の中央に配置するためのウィジット |
Column | 縦に並べるためのウィジット | |
Row | 横に並べるためのウィジット | |
Container | 背景色、余白、サイズ、装飾などを設定できる汎用的なウィジェット | |
コンテンツの表示 | Text | 文字列を表示するウィジェット |
Image | 画像を表示するウィジット | |
Icon | アイコンを表示するウィジット |
とりあえず、ウィジットの役割から分類すると、
などに分類できそうです。(他にもウィジットの種類はたくさんあると思いますが。)
Widgetの階層構造
実際に実装しながら、ウィジットの階層構造を理解してみます。
Dartは、Web上で簡単に実装が試せるDartPadというサイトがあります。簡単な実装の確認なら、DartPadで気軽に試せるので便利です。
DartPad
まずは、以下の様に実装してウィジットの階層の流れを把握します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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の特徴を以下にまとめます。
・状態を持たず単に決まったことを表示するだけ
・buildメソッドを実装して一つ以上のwidgetを構成する
◯StatefulWieget
・状態を持つ。Stateクラスを実装する
・buildメソッドはStateクラスで実装する
・状態を変化させるときは、setState内で実施。
・setStateをコールすると、自分自身で表示更新する(buildメソッドが再び呼ばれる)
StatelessWidgetを実装する
それでは、StatelessWidgetを実装してみます。
さっき作ったプログラムの箱を表示する部分をStatelessWidgetとしてクラス化して作ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
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を実装する
一番分かりやすくてよく例にでてくる定番のカウンター画面を作ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
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を実装していると少しずつ理解できるようになってきました。
やっぱり初めての言語を理解していくには、手を動かして実装するのが一番ですね。
それでは!!
スポンサーリンク