【Flutter/Dart】Widgetとは?StatelessWidgetとStatefulWidgetの違いと使い方

【Flutter/Dart】Widgetとは?StatelessWidgetとStatefulWidgetの違いと使い方

この記事からわかること

  • Flutter/DartWidgetとは?
  • Widgetのツリー(階層)構造仕組み
  • buildメソッド役割
  • StatelessWidgetStatefulWidget違い

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Widgetとは?

公式リファレンス:Widget

Flutterの「Widget」とはFlutterアプリのUIを構成する基本的な要素です。ボタンやテキスト、画像、レイアウトなどUIに関わる要素がWidgetとして扱われており、それらを組み合わせることでアプリが完成します。

またiOSやAndroidなどと同じようにWidgetもツリー(階層)構造で管理されています。基本的に親Widgetがあり、親Widgetが子Widgetを持つという構造になっています。例えば以下のようなUIが実装されているとします。

Scaffold(
  appBar: AppBar(title: Text('Flutter App')), // AppBarウィジェット
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Hello, Flutter!'), // Textウィジェット
        ElevatedButton(
          onPressed: () {},
          child: Text('Click Me'), // ボタン内のTextウィジェット
        ),
      ],
    ),
  ),
)

その場合、ツリー構造はScaffoldをルートとして子どもにAppBarCenterを保持しているような構造になります。

Scaffold
  ├── AppBar
  │    └── Text('Flutter App')
  └── Center
      └── Column
          ├── Text('Hello, Flutter!')
          └── ElevatedButton
              └── Text('Click Me')

Widget自体の構造

例としてText Widgetを参考に構造を確認してみます。Widgetの実態はクラスでありStatelessWidget(後述します)を継承しています。中には初期化するためのコンストラクタと実際にWidgetを構築して返却するbuildメソッドが記述されています。


class Text extends StatelessWidget {
  /// コンストラクタ(初期化する際に色々な引数を受け取れる)
  const Text(
    String this.data, {
    super.key,
    this.style,
    this.textAlign,
    ...
  });

  /// 表示する文字列
  final String? data;

  /// テキストのスタイル(フォントサイズ、色など)
  final TextStyle? style;

  /// UI(Widget)を構築する
  @override
  Widget build(BuildContext context) {
    TextStyle? effectiveTextStyle = style;
    Widget result = RichText(
      text: TextSpan(
        style: effectiveTextStyle,
        text: data,
      ),
    );
  }
}

Widgetの種類

Widgetは大きく分けてStatelessWidgetStatefulWidgetに分かれます。両者の違いは名前の通り状態(State)を保持するか否かで、Flutterでは抽象クラスとして定義されています。そのためどちらかを継承した状態でWidgetは定義されます。

StatelessWidget

StatelessWidget変化のない固定のUIを構築する際に活用されます。例えばヘッダーのタイトルやボタンタイトルなど更新することのない箇所にはStatelessWidgetを使用します。

StatelessWidgetはシンプルにコンストラクタとbuildメソッドが実装されているだけです。


class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

StatefulWidget

StatefulWidget頻繁に更新したいUIをを構築する際に活用されます。例えばカウンターや入力フォームなど任意のトリガーにおいてUIを更新したい箇所にはStatefulWidgetを使用します。

例えば以下は「ボタンを押下するごとにカウンターが増加するUI」です。この一連のWidgetはStatefulWidgetを継承したクラスとState<MyHomePage>を継承したクラスの2つが存在することがわかります。

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // 状態を保持
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // 状態を更新
      _counter++;
    });
  }

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

コードを少し分解しながら読み解いていきます。まずはStatefulWidgetを継承しているクラスです。createStateメソッドをオーバーライドして任意のStateインスタンスを作成し、状態を管理します。つまり状態を保持し、更新処理を行うのはStateクラスが実態ということになります。

class MyHomePage extends StatefulWidget {
  // コンストラクタ
  const MyHomePage({super.key, required this.title});
  // 呼び出し時に指定されるタイトル
  final String title;

  // Stateインスタンスを作成
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

次は先ほど定義したWidget型を管理ためのState<MyHomePage>を継承したクラスです。Stateクラスもbuildメソッドを保持しており、ここでUIの構築を行います。ポイントになるのは状態を保持するための変数_countersetStateメソッドです。setStateメソッドは状態を更新するためのメソッドこの中で状態を更新することでWidgetが再描画される(buildメソッドが再度呼ばれる)ようになります。

class _MyHomePageState extends State<MyHomePage> {
  // 状態を保持
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // 状態を更新
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 〜〜〜〜〜〜省略
      body: Center(
        child: Column(
          
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              // 状態をUIと紐付け
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      // ボタンに`setState`で状態を更新する`_incrementCounter`メソッドを紐付け
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

あとはUI側と紐づけるために$_counterTextと紐付け、ボタン押下で状態が変化するようにFloatingActionButton_incrementCounterメソッドを紐付けています。これでボタンを押下して状態が変化すると即座にUIに反映される実装が完了しました。

Flutterとは?インストール〜開発環境構築までの手順!プロジェクト作成

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index