2021/08/27

【flutter】Widgetについて説明する回

@ 古曳 純基

Flutter

今日から、Flutterの復習がてらノートがわりに記事書こうと思います。
一緒に成長していきましょう!

Widgetとは何か

現段階での私の理解としては、Widget(ウィジット)とは一言でまとめると「Flutterアプリケーションを構成する部品」のことです。
Flutterでのアプリケーション開発において以下のものがウィジットとして表現されます。

  • テキスト要素
  • 画像
  • アイコン
  • ウィジットを配置、整列する行やレイアウト、グリッド
  • テキストのスタイリング
  • アニメーション
  • 非同期処理によって返ってくるデータ

など...

公式ドキュメントにウィジットのカタログがあるので参照してみてください。
https://flutter.dev/docs/development/ui/widgets

StatelessWidgetとStatefulWidget

それぞれの機能について説明していきます。

StatelessWidgetとは何か

StatelessWidgetとは直訳すれば、状態を持たないウィジットになります。
ウィジットが状態を持たないため、構造が簡単であることが特徴と言えます。
以下にStatelessWidgetであるMyAppの実装を示します。
コードの中でわかりにくい点がいくつかあると思うので、説明しておきます。

  • buildメソッドとは、表示したいUIを表現するウィジット(デベロッパーがカスタムしたMateralAppウィジット)のインスタンスを返すメソッドです。
  • Themeでは、各ウィジットに対して適用したいテーマの情報を渡します。内部のウィジットにてtheme.of(context)によってテーマの情報を受け取ることができます。こちらの概念はかなり奥が深いため、こちらの記事を参考にしてください。=>https://zenn.dev/sugitlab/articles/bef3a05963680a
  • Scaffoldについて公式ドキュメントを読んでみたところ、Scaffoldウィジットはスマホのキーボード機能を考慮しており、キーボードが表示された際にいい感じにレイアウトを再描画してくれるウィジットっぽいです。詳しい内容はこちらからみて見てください。=>https://api.flutter.dev/flutter/material/Scaffold-class.html


//material.dartを読み込むことでflutterの基本的な機能を使用可能にする
import 'package:flutter/material.dart';


//MyAppウィジットを実行している
//(アプリの起動時に動作する部分)
void main() => runApp(MyApp());


//MyAppをstatelessで作成
class MyApp extends StatelessWidget {
  //StatelessWidgetクラスのbuildメソッドをオーバーライド(上書き)する
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Application name
      title: 'Flutter Hello World',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // A widget which will be started on application startup
      home: Scaffold(
        body: Center(
          child: Text(
            "This is StatelessWidget!",
            style: Theme.of(context).textTheme.display1,
          ),
        ),
      ),
    );
  }
}


こちらのコードの実行結果を以下に示します。

状態を持たないのでユーザーとのインタラクション性は何もないアプリケーションができました。

StatefulWidgetとは何か

StatefulWidgetとは、状態を有するウィジットのことでユーザーのなんらかのアクションによって再描画が走るようなウィジットのことです。Reactを学習している人ならピンと来ると思います。Reactにおいても、コンポーネント(Reactにおけるウィジット的なもの)はstateという変数を持っており、その変数が書き変わることでコンポーネントのレンダリングが走ります。

FlutterにおいてStateとStatefulWidgetは以下のような関係となっています。
Stateウィジットが状態の保持とウィジットの描画の両方を担当します。
StatefulWidgetは、ただ内部でcreateStateメソッドを実行してStateを作成します。
StatelessWidgetとは異なり、StatefulWidgetは内部にbuildメソッドを持たない点がかなり予想外の実装ですよね。

ここではカウンターを用いて、軽く実装の説明をしようと思います。
以下に実装を示します。わかりにくいと感じた点はコメント文で書いておいたのでコードを読んでみてください。

//material.dartを読み込むことでflutterの基本的な機能を使用可能にする
import 'package:flutter/material.dart';


//MyAppウィジットを実行している
//(アプリの起動時に動作する部分)
void main() => runApp(MyApp());


//StatefulWidgetの実装
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}


//Stateを継承するクラスの名前は"_"からはじまる
class _MyAppState extends State<MyApp> {
  //この場所でメンバ変数を定義する
  //メンバ変数は"_"から始めましょう
  int _count = 0;


  //Stateクラスbuildメソッドをオーバーライド = 描画の機能を担う
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Application name
      title: 'Flutter Hello World',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // A widget which will be started on application startup
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Press button'),
              Text(
                //変数呼び出しの際は$をつける
                '$_count',
                style: Theme.of(context).textTheme.display1,
              )
            ],
          ),
        ),
        //カウントアップボタンの実装
        //Centerウィジットと同階層
        floatingActionButton: FloatingActionButton(
          //onPressedというイベントハンドラが発火した際に走る処理
          onPressed: _incrementCountMethod,
          //ボタンホバー時に表示される文字
          tooltip: "Increment count!",
          //ここで"+"のアイコンを渡している
          child: Icon(Icons.add),
        ),
      ),
    );
  }


  //イベントハンドラに仕込む関数の定義
  void _incrementCountMethod() {
    setState(() {
      _count++;
    });
  }
}


このコードの実行結果は以下のようになりました。
<ボタンを押す前>
まだ何もやってないので、初期値としてセットした0が表示されていますね。


<ボタンを押した後>
右下にあるボタン(FloatingActionButton)を押してみました。
イベントハンドラに仕込んだ関数が走って状態が書き換わったことで、再描画が発生し画面が変わったことがわかります。


実際に挙動を確認してみて、StatelessWidgetとStatefulWidgetがなんたるかを理解できました。

レイアウト構築もウィジットにお任せ

冒頭でも述べましたが、ウィジットはレイアウトの構築もできます。
先程のコードの中でも度々出てきたCenterやColumnもウィジットなのです。

Widgetは大きく分ければこの2つ

Widget library
汎用的なウィジットのことだよ。どのようなアプリであっても使用可能だよ。
Material library
MaterialAppウィジットの提供機能に依存するから、MaterialAppを使用したアプリでしか使用できないよ。

基本的なレイアウトウィジット一覧

ここからは基本的なレイアウトウィジットについて特徴と実装例を用いて説明しようと思います。
Cupertino widget
IOS風のデザインを実現できる特徴があります。ですが、Flutterの良さはクロスプラットフォーム開発にあるのでこの機能はそこまで重要視する必要はないようです。
Column
列を表現するためのウィジットです。
複数の子要素を渡すことが可能です。
Row
行を表現するためのウィジットです。
複数の子要素を渡すことが可能です。
Container ( Widgets library )
padding,border,marginの設定が可能なウィジットで、背景(画像、色)を変更することができます。
一つの子ウィジットを持ちます。
こちらのContainerについてはHTML&CSSのボックスモデル的な機能を実装してくれる魔法の箱という風に理解していただければ良いと思います。

GridView ( Widgets library )
ウィジットをグリッド上に表示することが可能です。
列の長さが表示領域を超えたら自動的にスクロールできるようにしてくれます。
グリッド作成で使用するAPIは以下のものがあります。

  • GridView.count:一列に内包する要素数を指定
  • GridView.extent:タイルの最大幅の指定


ListView ( Widgets library )
コンテンツが表示領域を超えたときに自動的にスクロールできるようにしてれるウィジットです。

Stack ( Widgets library )
ベースウィジットの上に他のウィジットを重ねて配置することができるウィジットです。
使い方としてはchildrenに重ねて配置したいウィジットたちを渡します。ベースとしたいウィジットはchildrenの配列の最初に渡しましょう。
このウィジットを使用するとスクロールができなくなるようなので注意しましょう。

Card ( Material library )
こちらのウィジットはMaterial Designにおけるcardを実装してくれるウィジットのようです。
参考までにMaterial Designのサイトをご覧ください。=>https://material.io/components/cards
こんな感じのデザインをこのウィジットで実装できるとかすごすぎますよね。
Cardウィジットは子要素を1つ持ちますが、ColumnやRowなどの子要素を複数持つウィジットを追加することで複数の要素を持たせることが可能です。
また、標準ではサイズがない状態(0px × 0px)であるためSizeBoxウィジット(https://api.flutter.dev/flutter/widgets/SizedBox-class.html)によってサイズを決めてあげる必要があるそうです。

ListTile ( Material library )
ListTileは最大3行のテキストと左右の要素で構成されるレイアウトを表現するウィジットのようです。
左右の要素は、アイコンを埋め込むために利用されることが多いようです。

以上が一般的なレイアウトウィジットになります。
もっと知りたい方は公式ドキュメントの方にたくさん載ってますでで見てみて下さい。

Alignmentという概念を理解する

ColumnウィジットやRowウィジットを使用する上で理解しておきたいのがAlignmentという概念です。
正直なところ、CSSにおけるflexを理解されていればこの概念は朝飯前です。
ColumnウィジットとRowウィジットにはmainAxisAlignmentとcrossAxisAlignmentという二つの引数を指定することができます。
mainAxisAlignmentが主軸方向の配置になります。
crossAxisAlignmentが交差軸方向の配置になります。
イメージで表すと以下のようになります。

レイアウトで考えるべきこと

flutterにおいてレイアウトは以下の手順で組んでいくようです。
1. 基本的なウィジットの記述(テキストウィジットなど)

Text('This is main widget')

2. 基本的なウィジットをレイアウトウィジットのchildもしくはchildrenに入れる(子要素として渡す)

Center(
  child: Text('This is main widget')
)

3. レイアウトウィジットをページに追加する

//material.dartを読み込むことでflutterの基本的な機能を使用可能にする
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Flutter',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          body: Center(
            child: Text(
              'This is main widget',
              style: Theme.of(context).textTheme.display1,
            ),
          ),
        ),
      );
}


上記の例ではシンプルな構造のレイアウトでしたが、実際には複雑なレイアウトを組んでいくことになります。
複雑なレイアウトを組む場合、HTMLなどにおけるDOMのようにツリー構造を意識するとわかりやすいです。

最後に

ノートの代わりに記事を書いてみましたが、意外と楽しいので今後もflutter seriesをこんな感じで続けていこうかなと考えています。
おつかれさまでした。

参考文献

・Flutter | Flutter documentation
https://flutter.dev/docs 参照日2021.8.27
・南里 勇気、太田 佳敬、矢田 祐基、片桐 寛貴『Flutter モバイルアプリ開発バイブル』(株式会社リクナビ出版、2019)

© 2021 powerd by UnReact