flutter備忘録その２

# はじめに
とあるスマホアプリ開発のためにFlutterが必要となったのでそれの備忘録その2です。
色々なサイトなど参考にさせていただきまして、もしかしたらその内容に欠けてることもありますが、少しでも参考になればいいなと思います。

---



## Scaffoldについて
よく、ウィジェットと追加するさいにScaffoldの中に記述することが多い。
これはアプリの骨組みのようなイメージのウィジェットになる。
## Column Rowについて

### Column
ウィジェットを縦方向に1列に並べるウィジェット
childrenという要素を持ち、それの要素はリスト構造になっている
書き方イメージ
```dart
Column(
    children: [
    image1...
    image2...
    image3...
    ]
)
```
として子ウィジェットの中にあるリストを縦方向に表示する。

並べる順番の優先を決める要素
``mainAxisAlignment``この場合上から下を指すようになる

設定できるもの
| 値                          | 説明                                                                                      |
|-----------------------------|-------------------------------------------------------------------------------------------|
| `MainAxisAlignment.start`   | 子ウィジェットを上側（開始位置）に配置する。                                              |
| `MainAxisAlignment.end`     | 子ウィジェットを下側（終了位置）に配置する。                                              |
| `MainAxisAlignment.center`  | 子ウィジェットを中央に配置する。                                                          |
| `MainAxisAlignment.spaceBetween` | 子ウィジェット間のスペースを均等に分け、端に余白は作りません。                        |
| `MainAxisAlignment.spaceAround`  | 子ウィジェット間に均等なスペースを空け、端にも同じ大きさの余白を作る。            |
| `MainAxisAlignment.spaceEvenly`  | 子ウィジェット間および端の余白を均等に分ける。                                      |

**使用例画像(今回はcenterを利用)**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/dca33184-4f2b-8f85-7795-05cafe796c8d.png
" width = "200" />

<details><summary><strong>コード例</strong></summary>

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Column Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Columnで遊ぼう'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            width: 300,
            height: 300,
            color: Colors.red,
            child: const Center(
                child: Text('赤', style: TextStyle(color: Colors.white))),
          ),
          Container(
            width: 200,
            height: 200,
            color: Colors.green,
            child: const Center(
                child: Text('緑', style: TextStyle(color: Colors.white))),
          ),
          Container(
            width: 100,
            height: 100,
            color: Colors.blue,
            child: const Center(
                child: Text('青', style: TextStyle(color: Colors.white))),
          ),
        ],
      ),
    );
  }
}

```

</details>

### crossAxisAligment

子要素の位置を調整できる
**画像例**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/bd9a49db-7ab1-9aec-fa04-cf7ff6b9d270.png
" width = "200"/>

地味にだが子要素が右から始まっており
右したに全体のコンテナーがよっていることがわかる
これはAlignを利用して全体の要素を寄せたということです。

<details><summary><strong>コード例</strong></summary>

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Column Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Columnで遊ぼう'),
      ),
      body: Align(
        alignment: Alignment.bottomRight, // 右下に配置
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          mainAxisSize: MainAxisSize.min, // Columnのサイズを子ウィジェットに合わせる
          children: [
            Container(
              width: 300,
              height: 300,
              color: Colors.red,
              child: const Center(
                  child: Text('赤', style: TextStyle(color: Colors.white))),
            ),
            Container(
              width: 200,
              height: 200,
              color: Colors.green,
              child: const Center(
                  child: Text('緑', style: TextStyle(color: Colors.white))),
            ),
            Container(
              width: 100,
              height: 100,
              color: Colors.blue,
              child: const Center(
                  child: Text('青', style: TextStyle(color: Colors.white))),
            ),
          ],
        ),
      ),
    );
  }
}

```

</details>

### Row(横方向に並べてみる)
これまでColumnで縦方向に子要素を並べるように設定していたが横に並べてみる

つまりColumnをRowに書き換えたらいいだけ
**画像例**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/25f7f010-1cdd-5bed-5a28-f225cb8c4976.png
" width = "200" />

<details><summary><strong>コード例</strong></summary>
ほぼ同じだが

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Column Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Rowで遊ぼう'),
      ),
      body: Align(
        alignment: Alignment.center, // 右下に配置
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min, // Rowのサイズを子ウィジェットに合わせる
          children: [
            Container(
              width: 200,
              height: 200,
              color: Colors.red,
              child: const Center(
                  child: Text('赤', style: TextStyle(color: Colors.white))),
            ),
            Container(
              width: 100,
              height: 100,
              color: Colors.green,
              child: const Center(
                  child: Text('緑', style: TextStyle(color: Colors.white))),
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blue,
              child: const Center(
                  child: Text('青', style: TextStyle(color: Colors.white))),
            ),
          ],
        ),
      ),
    );
  }
}

```


</details>

## SingleChildScrolView
例えば画面からウィジェットの一部分がはみ出したとき
その一部分をスクロールして表示できるようにする

<details><summary><strong>コード例</strong></summary>

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Column Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Rowで遊ぼう'),
      ),
      body: SingleChildScrollView(
        scrollDirection: Axis.horizontal, // 横方向にスクロール可能
        child: Align(
          alignment: Alignment.center, // コンテンツを中央に配置
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min, // Rowのサイズを子ウィジェットに合わせる
            children: [
              Container(
                width: 400,
                height: 400,
                color: Colors.red,
                child: const Center(
                    child: Text('赤', style: TextStyle(color: Colors.white))),
              ),
              Container(
                width: 300,
                height: 300,
                color: Colors.green,
                child: const Center(
                    child: Text('緑', style: TextStyle(color: Colors.white))),
              ),
              Container(
                width: 200,
                height: 200,
                color: Colors.blue,
                child: const Center(
                    child: Text('青', style: TextStyle(color: Colors.white))),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

```
</details>

### Listview
先ほどまでのものでディフォルトで子ウィジェットがはみ出ててもスクロールできるウィジェット。

しかしリソース的にはすべて解釈しているので結構食べるみたい
<details><summary><strong>コード例</strong></summary>

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ListView Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ListViewで遊ぼう'),
      ),
      body: ListView(
        children: [
          Container(
            width: double.infinity, // 幅を画面全体に広げる
            height: 400,
            color: Colors.red,
            child: const Center(
              child: Text('赤', style: TextStyle(color: Colors.white)),
            ),
          ),
          Container(
            width: double.infinity, // 幅を画面全体に広げる
            height: 300,
            color: Colors.green,
            child: const Center(
              child: Text('緑', style: TextStyle(color: Colors.white)),
            ),
          ),
          Container(
            width: double.infinity, // 幅を画面全体に広げる
            height: 200,
            color: Colors.blue,
            child: const Center(
              child: Text('青', style: TextStyle(color: Colors.white)),
            ),
          ),
          Container(
            width: double.infinity, // 幅を画面全体に広げる
            height: 200,
            color: Colors.yellow,
            child: const Center(
              child: Text('青', style: TextStyle(color: Colors.white)),
            ),
          ),
        ],
      ),
    );
  }
}

```

</details>

# 画面遷移について

https://qiita.com/Walkdream24/items/354759e49b2e66b60bfe

この記事が体系的ですごくわかりやすいので見てみてください

## プッシュ遷移について
- 新しい画面を現在の画面にプッシュすること
- 元の画面は下にある状態
- 詳細とかもろもろ出すときに便利
- プッシュ画面の例:メモ帳(著作者がiPhoneのためiOSです...)
<img src= "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/284a7116-e57a-5b84-c7a7-2b9448fb45a4.png
" width="200"/>


今回の作成画像と遷移例
|遷移前 | 遷移後 |
|------------|----------|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/1c7bc24b-34a6-f031-8dc5-05ef701cc3a5.png" width="200"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/0daebca2-7ab6-e835-b267-dda365c114f3.png" width="200"/> |

**基本構文**
```dart
Navigator.push(
    
    context,     // 現在のページの状態
    
    MaterialPageRoute(
     builder:(context){ // 現在の情報を引数として渡す
     
        return // プッシュ遷移で遷移したいページ情報;
    
    }, // builder
    fullscreenDialog: false, // trueでモーダル遷移
   ), // MaterialPageRoute
 );  // Navigator.push
```
**以上のような典型的な書き方があります。**

この書き方を実践的な利用例に置き換えると

下記のサンプルコードを引用して
```dart
// ボタンが押されたら
onPressed: () { 
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context){
                    return const NextPage(); // ページ呼び出し
                }, // 次のページ呼び出し
                fullscreenDialog: false, // trueでモーダル遷移
              ), // MaterialPageRoute
            ); // Navigator.push
```
と書けます。




<details><summary><strong>コード例</strong></summary>

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ListView Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ), // ThemeData
      home: const MyHomePage(),
    ); // MaterialApp
  } // build
} // MyApp

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('プッシュ遷移で遊ぼう'), // appbar
      ), // AppBar
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              // 以下ボタン押されたときの処理
              context,
              MaterialPageRoute(
                builder: (context){
                 return const NextPage();
                }, // 次のページ呼び出し
                fullscreenDialog: false, // trueでモーダル遷移
              ), // MaterialPageRoute
            ); // Navigator.push
          },
          child: const Text("次のページへ"), // ボタン内に表示される内容
        ), // ElevatedButton
      ), // Center
    ); // Scaffold
  } // build
} // MyHomePage

// 遷移後のページのクラスを定義
class NextPage extends StatelessWidget {
  const NextPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 中身に由来するScaffold
      appBar: AppBar(
        title: const Text("NextPage"),
      ), // AppBar
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              "NextPageです",
              style: TextStyle(fontSize: 16),
            ), // Text
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text("戻る"),
            ), // ElevatedButton
          ], // children
        ), // Column
      ), // Center
    ); // Scaffold
  } // build
} // NextPage

```

</details>

## モーダル遷移
- 新しい画面が現在の上に表示されるもの
- モダール画面の例:メモ帳(著作者がiPhoneのためiOSです...)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/cccd46a3-11fd-ad19-7b30-5d50e0ef7d9b.png
" width = "200">


:::note warn
iOSとAndroidでは遷移の方向が違うので注意です。
ベゼルスワイプ(左端から一個ずつ戻っていく機能)はiOS限定(プッシュ遷移)

:::

### 画面間での値の受け渡し

遷移後に値を保持しないと割と大変です。
そこで遷移後に値を保持する方法を紹介します。


**例（プッシュ遷移)**
| ボタン押下前 | ボタン押下後 |
|--------|--------|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/e759d17e-3362-e770-4641-2c4b357ebb98.png" width="300"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/f01af6ef-d7e7-c018-3d5a-c60fe952f453.png" width="300"/> |

**例（モーダル遷移)**
| ボタン押下前 | ボタン押下後 |
|--------|--------|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/e759d17e-3362-e770-4641-2c4b357ebb98.png" width="300"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/f49b50d3-e840-16de-8b1d-bd76e62b0d9f.png" width="300"/> |

今回の例のように変数「id」の値を遷移によって変更する。

**考え方**
基本的には関数の操作における引数の受け渡しで値を管理する。
例えば「○○が押されると変数に値1が入る」このイメージである。

とはいえイメージがわきにくいので早速コードを見ていこう
※ 今回から見にくいのでファイルを分割しています
<details><summary><strong>コード例</strong></summary>

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

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

// エントリーポイントクラス
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Example',
      home: MyHomePage(),
    ); // MaterialApp
  } // build
} // MyApp
```

```TopPage.dart
import 'package:flutter/material.dart';
import 'NextPage.dart';

// Topページクラス
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  // プッシュ遷移関数
  void pushfunc(BuildContext context, {required id, required name}) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return NextPage(id: id, name: name); // 遷移先のページを指定
        },
        fullscreenDialog: false, // プッシュ遷移
      ), // MaterialPageRoute
    ); // Navigator.push
  } // pushfunc

  // モーダル遷移関数
  void modalfunc(BuildContext context, {required id, required String name}) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return NextPage(id: id, name: name); // 遷移先のページを指定
        },
        fullscreenDialog: true, // モーダル遷移
      ), // MaterialPageRoute
    ); // Navigator.push
  } // modal

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('プッシュ & モーダル遷移で遊ぼう'), // Text
      ), // AppBar

      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // プッシュ遷移のボタン
            ElevatedButton(
              onPressed: () {
                pushfunc(
                  context,
                  id: 1,
                  name: "プッシュ遷移で遷移した画面",
                );
              },
              child: const Text("プッシュ遷移 (通常遷移)"),
            ),
            const SizedBox(height: 16), // ボタン間のスペース

            // モーダル遷移のボタン
            ElevatedButton(
              onPressed: () {
                modalfunc(
                  context,
                  id: 2,
                  name: "モーダル遷移で遷移した画面",
                );
              },
              child: const Text("モーダル遷移"),
            ),
          ],
        ),
      ), // Center
    ); // Scaffold
  } // build
} // MyHomePage

```

```NextPage.dart
import 'package:flutter/material.dart';

// 遷移後のページ
class NextPage extends StatelessWidget {
  // コンストラクタ
  const NextPage({required this.id, required this.name});

  // メンバ変数
  final int id;
  final String name;

  // オーバーライド
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 中身に由来するScaffold
      appBar: AppBar(
        title: const Text("NextPage"),
      ), // AppBar

      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "NextPageです\n id = $id name = $name",
              style: TextStyle(fontSize: 16),
            ), // Text
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text("戻る"),
            ), // ElevatedButton
          ], // children
        ), // Column
      ), // Center
    ); // Scaffold
  } // build
} // NextPage
```
</details>

重要な部分だけ取り出して解説します。
(モーダル遷移も同じなのでまとめて解説します)
```TopPage.dart
// プッシュ遷移のボタン
ElevatedButton(
  onPressed: () {
    pushfunc(
      context,
      id: 1,
      name: "プッシュ遷移で遷移した画面",
    );
  },
  child: const Text("プッシュ遷移 (通常遷移)"),
),
```
ここではプッシュ遷移のボタンが押された際の動きを定義しています。
ElevatedButtonが押されたら

onPressedで遷移関数に値を渡してます。
遷移関数の内訳がこうなります
```TopPage.dart
// プッシュ遷移関数
  void pushfunc(BuildContext context, {required id, required name}) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return NextPage(id: id, name: name); // 遷移先のページを指定
        },
        fullscreenDialog: false, // プッシュ遷移
      ), // MaterialPageRoute
    ); // Navigator.push
  } // pushfunc
```
引数を解説すると

| **項目**   | **説明**                                             |
|------------|-----------------------------------------------------|
| `context`  | 現在の画面の情報を格納（必須項目）                  |
| `id`       | 今回変動させる値。名前付き引数で指定している。       |
| `name`     | テキストの内容を入れる変数。名前付き引数で指定している。 |

このようになってます。

ここで重要なのが **``fullscreenDialog: false, // ``**
この部分が「**false**」になっているので**プッシュ遷移**となる
ちなみに「**true**」だと**モーダル遷移**となる。
その後はコード例でも示している、「NextPage.dart」のクラスが実行される

---

今回、わかりやすさ優先で遷移ごとに関数を分けたがbool型で管理する事もできる
その場合、遷移関数が一つで済む。

bool型の利用例

**① 遷移関数の引数にbool型を定義する**
**② `` fullscreenDialog:``をflagで管理する**
```TopPage.dart
 // 遷移関数
  void Split(BuildContext context,
      {required id, required name, required bool flag}) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return NextPage(id: id, name: name); // 遷移先のページを指定
        },
        fullscreenDialog: flag, // プッシュ遷移
      ), // MaterialPageRoute
    ); // Navigator.push
  } // pushfunc
```

あとは引数でflagの内容を渡すだけです。

```TopPage.dart
ElevatedButton(
  onPressed: () {
    Split(
      context, 
      id: 1, 
      name: "プッシュ遷移で遷移した画面", 
      flag: false // 変更点
      );
  },
  child: const Text("プッシュ遷移 (通常遷移)"),
),
```

以上のような形で値を受け渡し等できます。

---

# 参考資料等

ChatGPT

https://openai.com/ja-JP/chatgpt/overview/

Udemy Flutterアプリ開発講座(初級編)

https://www.udemy.com/course/nzigen-flutter_beginner/?srsltid=AfmBOorTUwE2xGBNd8ijU4F_bXJlx0LvMrZTIT425A3j6JoRgczJ9ECD&couponCode=KEEPLEARNING

Fluteerで始めるアプリ開発

https://www.flutter-study.dev/widgets/about-widget

**備忘録その1**

https://qiita.com/nanashi39/items/e09b08da960b93afcd28

**Dart基礎文法**

https://qiita.com/nanashi39/items/0a7a65feeaaecec9b56d