Chapter 03

Flutterの基礎

にの
にの
2022.12.08に更新

プロジェクト作成

まずはコマンドプロンプトで好きなディレクトリに移動して以下のコマンドを入力してください。
「flutter_study」はプロジェクト名なので自分の好きな名前に変更して大丈夫です。

flutter create flutter_study

すると

Signing iOS app for device deployment using developer identity: "Apple
Development: ryota.swimmer@icloud.com (46T4L4V9BM)"
Creating project flutter_study...
Running "flutter pub get" in flutter_study...                    2,350ms
Wrote 127 files.

All done!
In order to run your application, type:

  $ cd flutter_study
  $ flutter run

Your application code is in flutter_study/lib/main.dart.

こんな感じの表示が出るので「All done!」が書いてたらOKです。
できたプロジェクトファイルをVSCodeなどのテキストエディタで開いてください。


ファイル構成はこんな感じで、「lib」ディレクトリがあるのですが基本的にはこのディレクトリで作業をしていきます。
「lib」ディレクトリの中にある「main.dart」というファイルを開いてください。

main.dart
import 'package:flutter/material.dart';

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

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

  
  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({super.key, required this.title});

  final String title;

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

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

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

  
  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),
      ),
    );
  }
}

(コメントアウト多すぎるので消してます。消すことをお勧めします。)

まずはプロジェクトをビルドしてみましょう。
コマンドプロンプトで以下のコマンドを入力してください。

flutter run


こんな画面が表示されたらOKです。

main.dartの構成

main.dartの基本構成を簡単に解説しておきます。

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

このmain関数がアプリを実行した時に最初に呼ばれる関数です。
そしてこのmain関数の中で「MyApp」というクラスが呼ばれています。

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

そしてまた「MyApp」クラスの中でreturnされている「MaterialApp」の中で「MyHomePage」クラスが呼び出されています。

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

  final String title;

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

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

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

  
  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),
      ),
    );
  }
}

まあなんせrunAppされてから、
MyApp -> MaterialApp -> MyHomePageの順番で動いてます。

Widget(ウィジェット)の紹介

以前Widgetの説明をしたときに、
「今はFlutterはWidgetを組み合わせて画面を作ってるんだなって思っててください。」
って伝えました。
「Widget」というのはいろんな種類のものがあります。
Widgetを知っていくために一旦「MyHomePage」クラスのbuild関数内をScaffoldのみにしてみましょう。

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

これで準備完了です。

Scaffold

Scaffoldウィジェットはアプリ画面を作る上でよく使われるメニュー表示方法を含めて画面全体を管理するWidgetです。
Scaffoldは「足場」という意味で、文字通りUIを作っていく上での「足場」となるので基本的には一番最初に書かれます。
たとえばScaffoldウィジェットにはappBarプロパティがあります。

 return Scaffold(
+  appBar: AppBar(
+    title: Text("プログラム研究部"),
+  ),
 );


appBarプロパティを追加してあげると、わざわざ1から作らなくても少ないコードでAppBarを実現できます。
便利便利。

Text

その名の通り文字を表示できるWidgetです。
Scaffoldの中にbodyプロパティを追加し、その中にTextウィジェットを書いてみましょう。

 return Scaffold(
   appBar: AppBar(
     title: Text("プログラム研究部"),
   ),
+  body: Text("ボタンを押すとカウントが1増えます。"),
 );


そうすると文字が表示されたかと思います。
そしてstyleプロパティを追加してあげることで色や文字サイズも変更することができます。

 Text(
   "ボタンを押すとカウントが1増えます。",
+  style: TextStyle(
+    color: Colors.blue, // 文字色
+    fontSize: 12, //フォントサイズ
+  ),
 ),

Center

今は左上に文字があると思うのですがちょっと見にくいのでCenterウィジェットを使ってみましょう。
Centerウィジェットは中にあるWidgetを中央に配置してくれます。
ほとんどのWidgetにはchildプロパティがあり、そこに新しいWidgetを追加していきます。

+ body: Center(
+         child: Text(
            "ボタンを押すとカウントが1増えます。",
            style: TextStyle(
              color: Colors.blue,
              fontSize: 12,
            ),
          ),
+       ),


こうすることでCenterウィジェットとTextウィジェットに親(Center)と子(Text)の関係ができました。
つまりCenterウィジェットはTextウィジェットに影響を与えることができます。
「子ウィジェットを中央に配置する」Centerウィジェットと「文字を表示する」Textウィジェットを組み合わせることで、「中央に配置される文字」が出来上がります。

Row&Column

たとえば文字(Text)を縦や横に複数置きたい場合、RowやColumnを使用します。
Rowウィジェットは複数の子ウィジェットを横に並べてくれます。
TextウィジェットをRowウィジェットで囲ってみましょう。

body: Center(
+       child: Row(
+         children: [
            Text(
              "ボタンを押すとカウントが1増えます。",
              style: TextStyle(
                color: Colors.blue,
                fontSize: 12,
              ),
            ),
+         ],
+       ),
      ),

childじゃなくて、childrenプロパティになっています。
childrenプロパティはWidgetを複数持つことができます。新しいTextウィジェットを追加してみましょう。

body: Center(
        child: Row(
          children: [
            Text(
              "ボタンを押すとカウントが1増えます。",
              style: TextStyle(
                color: Colors.blue,
                fontSize: 12,
              ),
            ),
+	    Text("2つ目のText"),
          ],
        ),
      ),


すると2つのTextウィジェットが横に並んでいると思います。
次にRowウィジェットをColumnウィジェットに変更してみましょう。

body: Center(
-       child: Row(
+       child: Column(
          children: [
            Text(
              "ボタンを押すとカウントが1増えます。",
              style: TextStyle(
                color: Colors.blue,
                fontSize: 12,
              ),
            ),
	    Text("2つ目のText"),
          ],
        ),
      ),


今度は横ではなく縦にWidgetが並んだと思います。
このようにして複数のWidgetを並べてくれるのがRowとColumnの特徴です。

Container

Containerウィジェットは基本的に単なるレイアウトボックスです。
四角や丸の形を作ったり、自由度の高いWidgetです。
Columnウィジェットの中にContainerウィジェットを追加してみましょう。

body: Center(
        child: Column(
          children: [
            Text(
              "ボタンを押すとカウントが1増えます。",
              style: TextStyle(
                color: Colors.blue,
                fontSize: 12,
              ),
            ),
+	    Container(
+	      width: 100,
+	      height: 100,
+	      color: Colors.blue,
+	    ),
-	    Text("2つ目のText"),
          ],
        ),
      ),

Button

モバイルアプリを作る上で必須のWidgetです。
画面遷移などの次の処理を実装していきます。
Buttonウィジェットにもいくつか種類がありますが、今回は「ElevatedButton」を紹介します。

body: Center(
        child: Column(
          children: [
            Text(
              "ボタンを押すとカウントが1増えます。",
              style: TextStyle(
                color: Colors.blue,
                fontSize: 12,
              ),
            ),
+	    ElevatedButton(
+             onPressed: () {},
+             child: Text("+1"),
+           ),
-	    Container(
-	      width: 100,
-	      height: 100,
-	      color: Colors.blue,
-	    ),
          ],
        ),
      ),


ボタンが表示されたと思いますが、これだと押してもまだ何も起こりません。
onPressedプロパティに処理を追加して、カウンターを表示するための変数とWidgetを配置しましょう。

class _MyHomePageState extends State<MyHomePage> {
+ int _counter = 0; // カウンター変数
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("プログラム研究部"),
      ),
      body: Center(
        child: Column(
+	  // Columnウィジェット内の子ウィジェットの配置位置を設定するプロパティ
+	  mainAxisAlignment: MainAxisAlignment.center,
          children: [
+	    // 現在のカウンター表示
+	    Text(
+             '$_counter',
+             style: TextStyle(
+               fontSize: 40,
+             ),
+           ),
            Text(
              "ボタンを押すとカウントが1増えます。",
              style: TextStyle(
                color: Colors.blue,
                fontSize: 12,
              ),
            ),
            ElevatedButton(
              onPressed: () {
+	        // 1回押されるごとにカウンターが1増える
+               setState(() {
+                 _counter++;
+               });
              },
              child: Text("+1"),
            ),
          ],
        ),
      ),
    );
  }
}

これでボタンを押すとカウンターの表示が1ずつ増えていく処理が完成しました。

以上でWidgetの紹介は終わりです。
他にも様々なWidgetがあるので、調べてみてください。

画面遷移

次に画面遷移の説明をしていきます。
まずは新しいファイル「second_screen.dart」を作成しましょう。

作成できたら、画面を作っていきます。

second_screen.dart
import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("次の画面"),
      ),
      body: Center(
        child: Text("SecondScreen"),
      ),
    );
  }
}

それではmian.dartのMyHomePageクラスからsecond_screen.dartのSecondScreenへ画面遷移させていきます。
まず画面遷移元のMyHomePageクラス内にSecondScreenへ画面遷移させるためのボタンを配置します。

ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SecondScreen()),
    );
  },
  child: Text("次の画面へ"),
),

onPressedプロパティ内に書かれている部分が画面遷移処理です。
MaterialPageRouteでSecondScreenクラスを指定しています。
しかしエラーが出ているかと思います。
それはmain.dartファイル内でsecond_screen.dartファイルを扱うためのimportができてないからです。
main.dartファイルの上の部分にimportの記述があるので、そこに新しくsecond_screen.dartファイルをimportします。

  import 'package:flutter/material.dart';
+ import 'package:flutter_study/second_screen.dart';

これでエラーが消えるので画面に反映し、ボタンを押してみてください。
無事画面遷移できたら成功です。

ツリー構造