Flutter入門の次にやること:Widget基礎の基礎編

Flutter入門で検索すると大体がインストールして起動するまで。

その先はどうしたらいいの……って思ってググるもいきなり中級者向けの話だったり そこで頓挫してしまう人も結構多いのではないでしょうか。

私も忙しいのも相まって2回ぐらい入門してほったらかしになってしまったことがあるので 今回はお仕事でも使うため本腰を入れて学んでいこうと思います。

で、入門はもう何回もしてるのでその次なにしたらいいんだよ ってなったのでそのメモ書きみたいな感じになります。

とりあえず画面内にウィンドウを出してみる

とりあえず画面内にウィンドウを出したいなと思います。

最終的にはウィンドウを掴みながら移動させて 縦横幅、X座標Y座標Z軸の設定なんかをサイドバーあたりで出来るようになればベストかなと思います。

とりあえず何をやるべきかは自分で決めるしかないので、 簡単なところでやってみたいことを上げてみるのが良いかなと思います。

ウィンドウを出すぐらいなら多分簡単だと思うので とりあえずまずはシンプルなウィンドウを出すだけをやってみましょう。

ディレクトリ構成なんかはどんな感じにしようとかはあとからで大丈夫です。

とりあえず入門してデフォルトのデモページがある状態から進めていきます。

私は出力はWeb(GoogleChrome動作)でやっていくので、 そこは各自好きな出力にしてください。

おそらく起動時するための設定意外はコードは共通で動くはずです。

main.dartを整理する

libディレクトリにあるデフォルトのmain.dartはDEMO用に色々書かれているので 一旦整理してスッキリさせましょう。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  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(
        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.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

コメントがずらっとあると難しく見えてしまうので一旦取り払いました。

元々はボタンを押したらカウンターが1増えていくものなので、 その部分を一旦取っ払って親ウィンドウが出るだけ(マテリアルデザインは適用のまま)にしてみます。

void main の部分がFlutterプログラムスタート地点なので そこで読み込んでるMyAppが起点となっているので、 その中で更にステートフルのウィジェットやらを読み込んでいるため とりあえずステートフルのウィジェットを読み込まないようにして動かしてみます。

まずはMyAppクラスを見てみましょう。

class MyApp extends StatelessWidget {
  // コンストラクタ
  const MyApp({Key? key}) : super(key: key);

  // 既存のものをオーバーライド
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

こんな感じになっています。

home: const MyHomePage(title: 'Flutter Demo Home Page'),

この部分が別のクラスを読み込んでいる部分なのでここを修正すれば良さそうです。

Widgetのすぐ下にあるreturnの隣りにあるMaterialAppは最初にインポートしている

import 'package:flutter/material.dart';

の部分で使えるようになっているだけですので今は気にしなくても大丈夫です。

話を戻しますと、MaterialAppクラスの中にあるhomeという引数にウィンドウの中身を渡して表示しているということになりますので この部分をなにもない中身を渡せばシンプルな親ウィンドウだけの状態になるはずです。

とりあえず空のものを渡すためにはどうすればいいかを見ていきましょう。

まずhomeは何を受け取るための引数7日を知る必要があります。

MaterialAppにカーソルを合わせてみるとどうやら「Widget? home」となっているので Widgetクラスを渡せばよさそうですが、?が就いているので渡さなくても動いてくれるようです。

?がついているのは引数なしでも動いてくれます。

とりあえずhomeの引数をなしで動かしてみます。

MyAppクラスは以下のようになりました。

class MyApp extends StatelessWidget {
  // コンストラクタ
  const MyApp({Key? key}) : super(key: key);

  // 既存のものをオーバーライド
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

homeをコメントアウトしただけです。

それ以外のクラスもコメントアウトしてもしてなくてもOKです。

Flutter runで起動すると赤文字でnull関連の警告をしてくれますが 一応ブラウザが立ち上がって真っ白な画面の右上にデバッグのリボンがついているので ブランクなウィンドウが出来上がりました。

ウィジェットを挿入してみる

真っ白なキャンバスが出来上がったのでここからとりあえず ステートを持つか持たないかはまず考えずにとりあえずウィジェットを生成して homeの引数に渡して動かすところまでやってみましょう。

最初に言ったとおり内部ウィンドウ的なものをだしてみます。

その前に少しFlutterのお勉強をしましょう。

ステートフルとステートレスの概念

FlutterはUIは基本Widgetを継承して作っていきます。

その際ウィジェットには「ステートフル」と「ステートレス」に分けられるので 値を書き換えたり更新したいものは基本「ステートフル」を利用します。

逆に1回描写してその後一切更新しないようないわゆる見た目用のUI等には 「ステートレス」を使う感じになりますね。

このあたりの感覚は実際に使っていって理解を深めていくのがベストです。

ステートフルなウィジェットでウィンドウを作る

今回ウィンドウは値を変えて座標や大きさなんかを変更したいので 「ステートフル」なウィジェットに該当します。

まずはステートフルなウィジェットクラスを作ってみましょう。

// ステートフルウィンドウウィジェットサンプル
class MyWindow extends StatefulWidget {
  // コンストラクタ
  const MyWindow({Key? key}) : super(key: key);

  // オーバーライド
  @override
  State<MyWindow> createState();
}

クラス名は任意です。 今回はMyWindowと名付けました。

これをウィジェットとして読み込んでも何も起こりませんが、 エラーがでない最低限の書き方になります。

ステートフルウィジェットにしたいのでextendsでStatefulWidgetを継承する必要があります。

クラスにはコンストラクタというものが必要なのでクラス名とおなじ命名で定義します。

StatefulWidgetを継承すると最低限やらなきゃいけないことは コンストラクタで親のコンストラクタを呼び出す必要があります。

super(key:key)の部分ですね。

これはMyWindowにkey引数に渡すと親引数に伝わるイメージと思ってくれたら大丈夫です。

Keyに?がついているため、逆に何も指定しないと親側で設定されたkeyを使用しますので 絶対に引数にKeyを指定する必要はないということですね。

次にオーバーライドです。

これで親のものを上書きするといった感じです。

ウィジェットクラスを継承する条件は親クラスのコンストラクタを継承して、 なおかつ親クラスのメソッドをオーバーライドすることです。

この辺はクラスで組んでいくプログラミング言語にはよくありがちな形ですね。

JavaScriptだけをやっていると継承して~っていう作業があまりないので気をつけましょう。

現状のコード全体はこんな感じになっています。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // コンストラクタ
  const MyApp({Key? key}) : super(key: key);

  // 既存のものをオーバーライド
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyWindow(),
    );
  }
}

// ステートフルウィンドウウィジェットサンプル
class MyWindow extends StatefulWidget {
  // コンストラクタ
  const MyWindow({Key? key}) : super(key: key);

  // オーバーライド
  @override
  State<MyWindow> createState();
}

homeにさきほど作ったウィジェットをあてがいました。

言ったとおり何も表示されない状態です。

そりゃウィジェットを引き継いだクラスを作っただけで中身を何も作ってないので当然ですね。

次はなにか表示してみましょう。

MyWindowの中身を作る

ウィジェットを作ったのでステートを管理するクラスを作ります。

本来はステート管理はそれだけ。

中身の表示は別クラスっていう感じに切り分けるべきなのですが、 そこを気にしてしまうと初心者はなかなか先に進めないので とりあえず一緒くたに記載していきます。

// MyWindowのステートを管理するクラス
class _MyWindowState extends State<MyWindow> {

  @override
  Widget build(BuildContext context) {
  }
}

Stateは抽象クラスです。

抽象クラスは基本的に中身は無く、上書きするのが前提の仕組みです。

抽象クラス側で使うものの情報だけ準備したから継承するならここを上書きして使ってねっていう感じですね。

意味合い的にはこの抽象クラスを継承しているなら確実にこのメソッドを持っているな っていう判断が出来るのがいいところでしょうか。

今回で言えばbuild関数をオーバーライドする必要があります。

正直私も抽象クラスをガンガン活用して作ったことはないのでそういう認識でいてます。

抽象クラスについて詳しく知りたい人はググってみてください。

それではなにか表示するためにbuild関数の中身を入れてみましょう。

コードは以下のようになります。

// MyWindowのステートを管理するクラス
class _MyWindowState extends State<MyWindow> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('テストタイトル'),
        centerTitle:true,
        width:2003
      ),
      body: Container(
        width:200,
        height:100,
        color: Colors.green,
      ),
    );
  }
}

build関数の中でScaffoldという関数を使ってreturnしています。

Scaffoldは足場という意味でようは部品を構成するための関数ですね。

これはもう仕様なのでこうやって使うんだって覚えておくと良いでしょう。

Scaffold関数の中を注目してください。

まずはappBarという引数です。

appBarはその名の通りウィジェットのタイトルバーになる部分ですね。

関数には1行目でインポートしてるものからAppBar関数を使って設定します。

いわゆるマテリアルデザインのヘッダを自動的にスタイリングしてくれます。

AppBar関数の中にはタイトル文字列やセンタリングするかどうかの設定があるので 一旦タイトルとセンタリングを指定しました。

指定しなくても動きますがサンプルなので一応付けてます。

次にbodyです。

ここが中身の部分になりますので、一旦横200px縦100pxと背景色だけを持つ シンプルな構成のコンテナを作りました。

作ったものをウィジェットに適用する

中身を作り終えたら次はこのクラスを先程のステートフルウィジェットで呼び出します。

// ステートフルウィンドウウィジェットサンプル
class MyWindow extends StatefulWidget {
  // コンストラクタ
  const MyWindow({Key? key}) : super(key: key);

  // オーバーライド
  @override
  State<MyWindow> createState() => _MyWindowState();
}

creawteStateのところにアローで先程のクラスを指定してます。

クラスの前にアンダーバーがついているのはローカルで使うものって覚えておくと良いでしょう。

他で呼び出されないようにprivateを付けたりするべきですが、今回は付けてません。

決まりではないですがコーディング規約的なのがあると思うので とりあえず元々がこう書かれていたので真似てこう書いているだけなのでご了承を。

これで起動してみると、タイトルバーと緑の矩形が表示されるはずです。

ウィジェット表示の基礎としてはここまでになります。

ちょっと長くなってしまったので自由に動かせるウィンドウは次にしようと思います。

とりあえず今回はFlutter入門の次にやることとしてお題目を上げました。

ウィジェットを出すのはFlutterの基本中の基本の作業で最後まで使うので しっかりと覚えておきましょう。