リファレンス
Reference

StatefulWidgetとStatelessWidgetの違い

URLをコピーする Twitterでシェアする Facebookでシェアする

Flutterの基礎である2つのウィジェット(StatefulWidgetとStatelessWidget)を理解しよう!

Flutterの基本的な構造

まず初めに、FlutterのUIはすべてウィジェットで構成されています。 ウィジェットは、Flutterで画面に表示される要素や画面の構造であるレイアウトを表現するために使用されます。

つまり、画面上のボタン、テキスト、画像など、表示される全ての要素はウィジェットとして扱われます。また、これらのウィジェットを組み合わせることによって、さまざまなUIが表現できます。

ウィジェットとツリー構造

Flutterのウィジェットは、木のような構造をしています。イメージとしては、木の根から枝が広がっていく感じです。この構造を「ツリー構造」と呼び、ウィジェット同士の階層的な関係を表現する方法です。

ツリー構造を使うと、ウィジェットの配置や動作を整理しやすくできます。

レシピでイメージしてみましょう。まず、レシピ(親)が一番上に位置し、その下には材料や手順(子)が続きます。さらに、材料や手順の(子)下には、材料の分量や手順の詳細(別の子)を追加できます。

例えば、画面全体を表す親ウィジェットの下に、ボタンやテキストフィールドなどの子ウィジェットを配置できます。そして、ボタン内部にはアイコンやラベルなどの別の子ウィジェットを配置することも可能です。

このように、ツリー構造を使うことで、ウィジェット同士の関係が明確になります。親ウィジェットが子ウィジェットを管理し、操作や情報の伝達がスムーズに行われます。

ツリー構造は重要

ツリー構造を理解することはとても重要です。なぜなら、ウィジェットの関係や階層が把握できるようになると、アプリのデザインや動作を簡単に作成できるようになるからです。

次の画像は、ツリー構造を表現したものです。 この画像は、ウィジェットの階層関係を視覚的に理解するためのものです。画像を見ることで、ウィジェットの親子関係や階層構造を把握ができます。

上記の例では、親としてMyAppを指定し、その直下には、MaterialAppが来ます。マテリアルデザインのアプリを開発する場合は、このウィジェットを使用します。 MaterialAppにはhomeというプロパティがあり、アプリ起動時にどの画面(ウィジェット)を表示するかを設定します。今回指定しているのはMyHomePageです。 さらにその直下にはScaffoldが来ています。

Scaffoldとは、レイアウト用のウィジェットです。Scaffoldは「白紙のキャンバス」であり、他のウィジェット「絵の具」のような役割を果たします。 Scaffoldというキャンバスにボタンやテキスト等のレイヤーをどんどん置いていって進めるのがFlutterでUI構築する際の基礎になります。

Scaffoldの各プロパティにはそれぞれ、AppBarCenterFloatingActionButtonというウィジェットを設定しています。 また、AppBarにはTextCenterにはColumnFloatingActionButtonにはIconが来ています。さらに、Columnの直下には、Textが来ています。

このように、Flutterはウィジェットをツリー構造として組み合わせていくということが理解できました。

基本的なウィジェットの紹介

それでは、2種類のウィジェットについて解説します。 Flutterには、よく使われる基本的なウィジェットとして2つのタイプがあります。

  • StatelessWidget
  • StatefulWidget

StatelessWidget とは

StatelessWidgetは、状態(state)を持たないウィジェットです。 そのため、上位のウィジェットのステートが更新されたときは、基本的にリビルドが走ります。

例えば、テキスト、アイコン、イメージなどのウィジェットがあります。

基本的なコードの書き方

こちらは、StatelessWidgetの基本的なコードの書き方です。

class クラス名 extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Container(

    );
  }
}

コードの詳細

さらにコードについて詳しく見ていきましょう。

クラス名を指定し、StatelessWidgetを継承しています。StatelessWidgetは、状態(state)を持たないウィジェットを作るための基本的なクラスです。

class クラス名 extends StatelessWidget {

ウィジェットのビルド(構築)を行うためのメソッドの定義です。BuildContext contextという引数を受け取ります。

@override
Widget build(BuildContext context) {

build()メソッドから返される値で、コンテナウィジェットを返します。コンテナは他のウィジェットを包むための箱です。 コンテナの中には、さらにテキストやボタンなど他のウィジェットを追加することができます。

return Container()

StatefulWidget とは

StatefulWidgetは、状態(state)を持つウィジェットです。 これにより、ウィジェットの状態を変更することで画面の再描画が行われます。ユーザーの入力やアプリの状態変化に応じて、動的なUIを作成するのに適しています。

例えば、チェックボックス、ラジオボタン、リストなどがあります。

基本的なコードの書き方

こちらは、StatefulWidgetの基本的なコードの書き方です。

class クラス名 extends StatefulWidget {
  @override
  _(クラス名)State createState() => _(クラス名)State();
}

class _(クラス名)State extends State<クラス名> {
  @override
  Widget build(BuildContext context) {
    return Container(

    );
  }
}

コードの詳細

さらにコードについて詳しく見ていきましょう。

クラス名を指定し、StatefulWidgetを継承しています。StatefulWidgetは、状態(state)を持つウィジェットを作るための基本的なクラスです。

class クラス名 extends StatefulWidget {

新しいステートオブジェクトを作成するためのメソッドです。

@override
  _(クラス名)State createState() => _(クラス名)State();
}

新しいステートオブジェクトのクラスを作成しています。

class _(クラス名)State extends State<クラス名> {

ウィジェットのビルド(構築)を行うためのメソッドの定義です。BuildContext contextという引数を受け取ります。

@override
Widget build(BuildContext context) {

build()メソッドから返される値で、コンテナウィジェットを返します。コンテナは他のウィジェットを包むための箱です。 コンテナの中には、さらにテキストやボタンなど他のウィジェットを追加することができます。

return Container()

まとめ

Flutterでは、さまざまなウィジェット(テキスト、アイコン、チェックボックスなど)を組み合わせてアプリを作り、ウィジェットツリーを構築してアプリの見た目や動作を決めます。そして、状態を持つウィジェットを使ってアプリのデザインや動作を変化させることができます。

ここからは、Flutterのサンプルアプリで使われているカウンターについて詳しく見ていきましょう。

サンプル画面

このサンプル画面は、Flutterプロジェクトを作成して「flutter run」と実行すると表示されるデフォルトのカウンターアプリです。 画面中央に数字が表示され、右下の+ボタンをタップすると数字が増えていきます。とてもシンプルな構造ですが、数字の変化を管理するために状態を持つウィジェットが使われています。

サンプルコード

以下は、上記のサンプル画面を構成するためのコードです(コメントは省略しています)。 コードを詳しく説明する前に、基本的な構造を見てみましょう。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }
}

さらにコードについて詳しく見ていきましょう。

main関数

はじめに、main関数を見てみましょう。

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

void main()関数は、アプリケーションのエントリーポイントです。runApp()関数を使用して、MyAppクラスを実行します。

MyAppクラス

次に、MyAppクラスを見てみましょう。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

MyAppクラスは、Flutterアプリケーションのルートとなるウィジェットです。build()メソッドでは、MaterialAppウィジェットを返しています。MaterialAppは、アプリケーションのテーマやタイトル、そしてMyHomePageクラスをホーム画面として設定しています。

MyHomePageクラス

次に、MyHomePageクラスを見てみましょう。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

MyHomePageクラスは、カウンターアプリのホーム画面を定義しています。StatefulWidgetを継承しており、状態を持つことができます。titleパラメーターは、ホーム画面のタイトルを表しています。

_MyHomePageStateクラス

次に、_MyHomePageStateクラスを見てみましょう。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

_MyHomePageStateクラスは、MyHomePageクラスの状態を表しています。_counter変数はカウンターの値を保持し、_incrementCounter()メソッドはボタンが押されたときにカウンターを増やします。

_MyHomePageクラス の build

次に、_MyHomePageState の buildを見てみましょう。

@override
Widget build(BuildContext context) {
  return Scaffold(
  //以下の内容は後ほど説明
    appBar: ...,
    body: ...,
    floatingActionButton: ...,
  );
}

build()メソッドでは、Scaffoldウィジェットを返しています。Scaffoldは、アプリケーションの基本的なビジュアルレイアウト用のウィジェットです。

appBar

次に、appBarを見てみましょう。

appBar: AppBar(
  title: Text(widget.title),
),

appBarは、アプリケーションの上部に表示されるバーを作成します。属性として、titleが指定されており、その内容が表示されます。ここでは、文字列要素のTextウィジェットが使われており、widget.titleが指定されています。

widget.titleは、StatefulWidgetであるMyHomePageに渡されたtitleを取得しています。Stateの内部であっても、このようにして呼び出したウィジェットの情報を取得できます。

body

次に、bodyを見てみましょう。

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text(
        'You have pushed the button this many times:',
      ),
      Text(
        '$_counter',
        style: Theme.of(context).textTheme.headline4,
      ),
    ],
  ),
),

Centerウィジェットで要素を中央に配置しています。コンテンツを中央寄せしたい場合に使用されます。この中にはchildという属性があります。Centerは単なるコンテンツのコンテナであるため、子要素が必要であり、childまたはchildren属性を持つ必要があります。この場合、子要素はColumnです。これにより、複数の要素を縦に並べることができます。

Columnは、リストや表のような要素を作成する際に便利なウィジェットです。mainAxisAlignment属性は、子要素を縦方向に並べる方法を指定するために使用されます。ここでは、Textウィジェットを使用して文字を設定しています。

style

次に、styleを見てみましょう。

Text(
  '$_counter',
  style: Theme.of(context).textTheme.headline4,
),

Textウィジェットにはstyleが設定されています。これは文字の大きさや色を指定するためのものです。

ここでは、Theme.of(context)を使用して親のテーマ情報を取得しています。contextは「文脈」という意味で、親のテーマ情報も保持しています。これにより、テキストに関連するテーマであるtextThemeheadline4スタイルを取得しています。

floatingActionButton

最後にfloatingActionButtonを見てみましょう。

floatingActionButton: FloatingActionButton(
  onPressed: _incrementCounter,
  tooltip: 'Increment',
  child: Icon(Icons.add),
  ), 
);

画面右下に浮動するボタンを作成するためのFloatingActionButtonウィジェットが含まれます。onPressedコールバックは、ボタンが押されたときに呼び出されるメソッドを指定します。ここでは_incrementCounterメソッドが指定されています。 これにより、Flutterアプリのホームページが作成され、ボタンが押されるたびにカウンターの値がインクリメントされ、画面が更新されるようになります。

まとめ

今回は、Flutterを使ったシンプルなカウンターアプリを解説しました。

基本的な部分を解説しましたが、Flutterの公式サイトにはアプリ開発に必要なドキュメントが数多くあります。新機能も定期的に追加されているため、常に最新情報をチェックすることが大切です。また、Flutterのコミュニティを活用すると、疑問点を解消することができるでしょう。

さらに、公式サイトでは多くのサンプルコードも掲載されていますので、実践的な学習に役立ててください。

参考リンク