【Flutter入門】基本ウィジェットから画面遷移まで実践コード付きで解説

プログラミング・IT

「Flutterを始めてみたいけど、何から手をつければいいかわからない」
「公式ドキュメントは英語だし、結局どのウィジェットを覚えればアプリが作れるの?」

そんな疑問を持つ方に向けて、この記事ではFlutterの基本ウィジェットから画面遷移、状態管理まで、実際に動くコード付きで段階的に解説します。

Flutterの開発は「ウィジェットというブロックを積み重ねる」感覚で進められるため、他のフレームワークと比較しても直感的に理解しやすいのが特徴です。
記事の最後には、学んだウィジェットを組み合わせた簡単なTODOアプリの作例も用意しています。
読み終わる頃には「自分でもアプリが作れそう」と感じられるはずです。


スポンサーリンク

Flutterの基本

まず、Flutterの前提知識を手短に押さえておきましょう。

Flutterとは

Flutterは、Googleが開発したオープンソースのUIフレームワークです。
プログラミング言語「Dart」を使い、1つのコードベースからiOS・Android・Web・デスクトップアプリを同時に開発できます。

2018年12月にバージョン1.0がリリースされ、2026年5月時点の最新安定版はFlutter 3.44(Dart 3.12)です。
四半期ごとに安定版がリリースされるため、バージョンは今後も更新されていきます。
Google Pay、Nubank、BMW、LG webOS TVをはじめ、多くの企業が本番アプリに採用しており、月間150万人以上の開発者が利用するフレームワークに成長しています(Google I/O 2026発表)。

Flutterの最大の特徴は、UIのすべてが「ウィジェット」で構成されるという設計思想です。
テキストもボタンも余白もレイアウトも、すべてウィジェット。
ブロックを組み合わせるような感覚で画面を作れるため、初心者でも「何が何を担当しているか」が把握しやすい構造になっています。

もう一つ、開発体験で特筆すべきなのがHot Reloadです。
コードを保存した瞬間、アプリを再起動することなく変更が画面に反映されます。
「テキストの色を変えて保存→即座に反映を確認→余白を調整して保存→また即確認」というサイクルが数秒で回るため、UIの試行錯誤が苦になりません。
実際に使ってみると、このスピード感は想像以上で、「ちょっと試してみよう」のハードルが一気に下がります。

環境構築の概要

Flutter開発を始めるには、以下の3つを準備します。

  1. Flutter SDKFlutter公式ドキュメントからダウンロード
  2. エディタ — VS Code(軽量で拡張機能が充実)またはAndroid Studio(エミュレータ管理が楽)
  3. プラットフォーム別ツール — Androidアプリ開発ならAndroid SDK、iOSならXcode

Scaffold — 画面の土台

Flutterアプリの各画面は、基本的に Scaffold というウィジェットの上に組み立てます。
Scaffold は画面上部の AppBar(タイトルバー)、メインの body、下部の BottomNavigationBar などを配置するための土台です。

Scaffold(
  appBar: AppBar(title: Text('マイアプリ')),
  body: Center(child: Text('ここがメインコンテンツ')),
)

この記事で後述するコード例はすべて Scaffoldbody 内に配置する想定で書いています。

なお、コード内では final(再代入不可の変数宣言)、const(コンパイル時定数)、=>(アロー関数)、?.(null安全アクセス)といったDart構文が登場します。
Dart未経験でも読み進められるよう配慮していますが、文法を体系的に学びたい場合はDart公式チュートリアルを併用してみてください。

インストール後、ターミナルで flutter doctor を実行すれば、環境に不足がないかチェックマーク付きで確認できます。

flutter doctor

全項目にチェックが付けば準備完了です。
環境構築の詳細手順はFlutter公式ドキュメント(英語)に記載されています。
日本語で進めたい場合は、有志運営の翻訳サイトFlutter Doc JPも参考になります。
ここではウィジェットの実践に進みましょう。


表示系ウィジェットの基本

Flutterの画面を構成する基本中の基本、「何かを表示する」ためのウィジェットです。

Text — 文字を表示する

もっともシンプルなのが Text です。
文字列を画面に表示し、style プロパティでフォントサイズ・太さ・色を調整できます。

Text(
  'こんにちは、Flutter!',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
)

TextStyle のプロパティは豊富ですが、最初は fontSizefontWeightcolor の3つを覚えておけば十分です。
複数行のテキストを省略表示にしたい場合は maxLinesoverflow: TextOverflow.ellipsis を組み合わせます。

Icon — アイコンを表示する

Flutterにはマテリアルデザインのアイコンが標準で組み込まれています
外部ライブラリをインストールする必要がなく、Icons.〇〇 で数千種類のアイコンにアクセスできます。

Icon(
  Icons.favorite,
  size: 32,
  color: Colors.red,
)

使えるアイコンの一覧は Material Icons で検索できます。
Flutterではアイコン名がスネークケース(例: Icons.arrow_back)になる点だけ注意してください。

Image — 画像を表示する

画像の表示方法は大きく2つあります。

ローカル画像(assetsフォルダ):

Image.asset('assets/images/logo.png')

pubspec.yamlassets フォルダのパスを登録する必要があります。

flutter:
  assets:
    - assets/images/

ネットワーク画像:

Image.network('https://example.com/photo.jpg')

ネットワーク画像は読み込みに時間がかかるため、本番アプリでは cached_network_image パッケージの利用がおすすめです。

Container — 装飾の万能ウィジェット

Container は背景色・余白・枠線・角丸など、見た目の装飾をまとめて担当するウィジェットです。
HTMLでいう div にCSSを当てたものに近い感覚で使えます。

Container(
  width: 200,
  height: 100,
  padding: EdgeInsets.all(16),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black26,
        blurRadius: 8,
        offset: Offset(0, 2),
      ),
    ],
  ),
  child: Text('カード風のデザイン'),
)

decorationBoxDecoration を渡すことで、角丸・影・グラデーションなど多彩な表現が可能です。
ポイントとして、colorContainer 直下と BoxDecoration の両方に指定するとエラーになります。
装飾を使うときは必ず decoration 内で色を指定してください。
これはFlutter初学者がほぼ確実に一度は遭遇するエラーで、エラーメッセージに Cannot provide both a color and a decoration と出たらこのパターンです。


レイアウトの組み方

表示系ウィジェットを覚えたら、次は「画面のどこに何を配置するか」を制御するレイアウトです。
Flutterではレイアウトもウィジェットです。
CSSのflexboxに相当する操作を、ウィジェットの入れ子で表現します。

Row・Column — 横並びと縦並び

Row は子要素を横方向に、Column縦方向に並べます。
Flutterのレイアウトで最も使用頻度が高い組み合わせです。

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('タイトル', style: TextStyle(fontSize: 20)),
    Text('サブタイトル', style: TextStyle(color: Colors.grey)),
    Row(
      children: [
        Icon(Icons.star, color: Colors.amber),
        SizedBox(width: 4),
        Text('4.8'),
      ],
    ),
  ],
)

mainAxisAlignment は並びの方向(Columnなら縦、Rowなら横)の配置を制御します。
crossAxisAlignment はその垂直方向の配置です。

覚えておくと便利な値は以下の3つです。

  • MainAxisAlignment.start — 先頭寄せ(デフォルト)
  • MainAxisAlignment.center — 中央揃え
  • MainAxisAlignment.spaceBetween — 両端に寄せて均等配置

SizedBox・Spacer・Expanded — 余白と伸縮

SizedBox は固定サイズの余白を作ります。
UI部品間にスペースを空けたいときに重宝します。

SizedBox(height: 16)  // 縦方向に16ピクセルの余白
SizedBox(width: 8)    // 横方向に8ピクセルの余白

Expanded は残りのスペースを埋めるように伸びる部品です。
Row や Column の中で使います。

Row(
  children: [
    Expanded(
      child: TextField(
        decoration: InputDecoration(hintText: '検索...'),
      ),
    ),
    SizedBox(width: 8),
    ElevatedButton(
      onPressed: () {},
      child: Text('検索'),
    ),
  ],
)

この例では、検索フィールドがボタンを除いた残りの幅をすべて使って伸びます。

SpacerExpanded の簡略版で、空白スペースを埋めるためだけに使います。

Row(
  children: [
    Text('左寄せのテキスト'),
    Spacer(),
    Text('右寄せのテキスト'),
  ],
)

Stack・Positioned — ウィジェットの重ね合わせ

Stack は子要素を重ねて配置します。
プロフィール画像の上にバッジを乗せたり、背景画像の上にテキストを表示するときに使います。

Stack(
  children: [
    Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
    Positioned(
      bottom: 0,
      right: 0,
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: Colors.red,
          shape: BoxShape.circle,
        ),
        child: Center(child: Text('3', style: TextStyle(color: Colors.white))),
      ),
    ),
  ],
)

Positionedtopbottomleftright を指定して、重ねる位置を細かく制御できます。

ListView — スクロール可能なリスト

画面に収まりきらない量のコンテンツを表示するとき、ListView を使います。

少数の固定アイテムなら直接 children に並べる書き方もあります。

ListView(
  children: [
    ListTile(title: Text('項目1')),
    ListTile(title: Text('項目2')),
    ListTile(title: Text('項目3')),
  ],
)

データ件数が多い場合は ListView.builder を使います。
画面に表示される分だけウィジェットを生成するため、メモリ効率が良くなります。

ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text('${index + 1}')),
      title: Text('アイテム ${index + 1}'),
      subtitle: Text('説明テキスト'),
    );
  },
)

ListTile はリスト1行分のレイアウトを簡単に作れるウィジェットで、leading(左端)・titlesubtitletrailing(右端)を指定するだけで整ったリスト表示になります。

初心者がハマりやすいポイント: Column の中に ListView をそのまま配置すると Vertical viewport was given unbounded height というエラーが出ます。
Columnは子要素のサイズを聞きますが、ListViewは「スクロールできるだけ無限に伸びたい」と答えるため、サイズが決まらず衝突するのが原因です。
解決策は、ListViewExpanded で包んで「残りのスペースを使って」と伝えることです。
このエラーはFlutter開発で非常によく遭遇するので、覚えておくとデバッグ時間を大幅に節約できます。

Padding — 内側の余白

Padding は子ウィジェットの周囲に余白を追加します。

Padding(
  padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  child: Text('余白付きのテキスト'),
)

EdgeInsets の主な使い方を整理しておきます。

  • EdgeInsets.all(16) — 上下左右すべて16
  • EdgeInsets.symmetric(horizontal: 16, vertical: 8) — 左右16、上下8
  • EdgeInsets.only(top: 8, left: 16) — 特定の方向だけ指定

外側の余白(margin)は Containermargin プロパティで指定します。
Padding専用ウィジェットと Container の padding の違いは機能的にはありませんが、装飾が不要なら Padding を使う方がコードの意図が明確になるでしょう。


操作系ウィジェット

画面を表示できるようになったら、次はユーザーの操作を受け取るウィジェットです。

ElevatedButton・TextButton・IconButton — ボタンの種類

Flutterのボタンは用途に応じて3種類を使い分けます。

// 塗りつぶしボタン(主要なアクション)
ElevatedButton(
  onPressed: () {
    print('タップされた');
  },
  child: Text('保存'),
)

// テキストのみのボタン(補助的なアクション)
TextButton(
  onPressed: () {},
  child: Text('キャンセル'),
)

// アイコンだけのボタン
IconButton(
  onPressed: () {},
  icon: Icon(Icons.delete),
)

すべてのボタンに共通するのは onPressed プロパティです。
ここにタップ時の処理を書きます。
onPressednull にするとボタンが無効化(グレーアウト)されるのも覚えておくと便利です。

TextField — テキスト入力

ユーザーからテキスト入力を受け取るには TextField を使います。
入力内容をプログラムから取得するには TextEditingController を使います。

class _MyFormState extends State<MyForm> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose(); // メモリリーク防止のため必ず破棄
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: _controller,
          decoration: InputDecoration(
            labelText: '名前',
            hintText: '名前を入力してください',
            border: OutlineInputBorder(),
          ),
        ),
        SizedBox(height: 16),
        ElevatedButton(
          onPressed: () {
            print('入力値: ${_controller.text}');
          },
          child: Text('送信'),
        ),
      ],
    );
  }
}

TextEditingControllerdispose() メソッドで破棄する必要があります。
これを忘れるとメモリリークの原因になるため、StatefulWidgetの dispose 内で必ず呼び出してください。

Checkbox・Switch・Radio — 選択系ウィジェット

ON/OFFの切り替えや選択肢の提示に使います。

// チェックボックス
Checkbox(
  value: _isChecked,
  onChanged: (bool? value) {
    setState(() {
      _isChecked = value ?? false;
    });
  },
)

// スイッチ
Switch(
  value: _isEnabled,
  onChanged: (bool value) {
    setState(() {
      _isEnabled = value;
    });
  },
)

どちらも現在の値(value)と変更時のコールバック(onChanged)をセットで渡す形式です。
この「現在の値を保持して、変更時にsetStateで更新する」パターンは、Flutterの状態管理の基本形なので、ここでしっかり馴染んでおきましょう。

GestureDetector・InkWell — タップ検知

ボタン以外の要素にタップ操作を追加したいときは GestureDetector または InkWell で包みます。

// タップの波紋エフェクト付き
InkWell(
  onTap: () {
    print('カードがタップされた');
  },
  child: Container(
    padding: EdgeInsets.all(16),
    child: Text('タップできるカード'),
  ),
)

InkWell はマテリアルデザインの波紋(リップル)エフェクトが付きます。
エフェクトが不要な場合や、タップ以外のジェスチャー(長押し、スワイプ等)を検知したい場合は GestureDetector を使います。


状態管理の基本(setState)

ここまでの内容は「表示するだけ」でしたが、実際のアプリではボタンを押したら数字が増える、チェックを入れたら表示が変わる、といった動的な変化が必要です。
Flutterで画面を動的に更新する最も基本的な方法が setState です。

StatelessWidgetとStatefulWidgetの違い

Flutterのウィジェットは2種類に分かれます。

  • StatelessWidget — 状態を持たない。一度描画したら変わらない(表示だけのUI)
  • StatefulWidget — 状態を持つ。ユーザー操作やデータ変更に応じて画面を再描画する

使い分けの基準はシンプルで、画面が変化する必要があるならStatefulWidget、ないならStatelessWidgetです。

// StatelessWidget — 変化しないUI
class Greeting extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('こんにちは');
  }
}

// StatefulWidget — 変化するUI
class Counter extends StatefulWidget {
  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('$_count', style: TextStyle(fontSize: 48)),
        SizedBox(height: 16),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text('カウントアップ'),
        ),
      ],
    );
  }
}

setStateの使い方

setState は「この中で変数を変更したら、画面を再描画してね」とFlutterに伝える命令です。
上のカウンター例では、ボタンを押すたびに _count が1増え、画面のテキストが即座に更新されます。

重要なルールが1つあります。
setState のコールバック内で変更する変数は、そのStateクラスのフィールドとして宣言すること。
build メソッド内のローカル変数を変更しても画面は更新されません。
実際にカウンターアプリを作ると、最初はつい build の中に変数を書いてしまい「タップしても数字が変わらない」と悩むことが多いです。
以下のNG例とOK例を見比べてみてください。

// NG — build内のローカル変数を変更しても反映されない
@override
Widget build(BuildContext context) {
  int count = 0; // 再描画のたびに0にリセットされる
  return ElevatedButton(
    onPressed: () {
      setState(() { count++; }); // 意味がない
    },
    child: Text('$count'),
  );
}

// OK — Stateクラスのフィールドを変更する
class _CounterState extends State<Counter> {
  int _count = 0; // ここで宣言

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        setState(() { _count++; }); // 正しく反映される
      },
      child: Text('$_count'),
    );
  }
}

setStateの限界と次のステップ

setState はシンプルで覚えやすい反面、アプリの規模が大きくなると課題が出てきます。

  • 親で setState を呼ぶと、子孫まで全体が再描画される
  • 離れたWidget間で状態を共有するのが難しい(バケツリレーになる)
  • ビジネスロジックとUIが混在しやすくなる

これらの課題を解決するのが、RiverpodBLoCProviderなどの状態管理パッケージです。
「setStateでは管理しきれないな」と感じたタイミングが、状態管理ライブラリに進むベストなタイミングです。
まずはsetStateで小さなアプリを一つ作り切ってから検討すれば十分でしょう。


画面遷移(Navigator)

アプリが1画面だけで完結することはほとんどありません。
Flutterでは Navigator を使って画面の遷移を管理します。

Navigator.pushとpop — 基本の画面遷移

新しい画面を開くのが push、前の画面に戻るのが pop です。

// 画面Aから画面Bに遷移
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SecondPage()),
    );
  },
  child: Text('次の画面へ'),
)

// 画面Bから画面Aに戻る
ElevatedButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: Text('戻る'),
)

MaterialPageRoute はプラットフォームに応じた遷移アニメーション(Androidはスライド、iOSは右からスワイプイン)を自動で適用してくれます。
Androidの端末に搭載されている戻るボタンでの遷移にも自動対応します。

画面間のデータ受け渡し

遷移先の画面にデータを渡すには、コンストラクタの引数を使う方法がもっとも簡単です。

// 遷移先の画面
class DetailPage extends StatelessWidget {
  final String title;
  final int id;

  const DetailPage({required this.title, required this.id});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text('ID: $id')),
    );
  }
}

// 遷移元で値を渡す
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(title: 'アイテム詳細', id: 42),
  ),
);

遷移先から遷移元にデータを返したい場合は、Navigator.pop の第2引数に値を渡し、push 側で await して受け取ります。

// 遷移先から値を返す
Navigator.pop(context, '選択されたアイテム');

// 遷移元で受け取る
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SelectionPage()),
);
print('受け取った値: $result');

名前付きルート(routes)

画面が5つ、10つと増えてくると、遷移のたびに MaterialPageRoute を書くのは煩雑です。
MaterialApproutes プロパティで画面を一覧管理すると、可読性が上がります。

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomePage(),
    '/detail': (context) => DetailPage(),
    '/settings': (context) => SettingsPage(),
  },
)

// 名前で遷移
Navigator.pushNamed(context, '/detail');

さらに画面管理が複雑になる場合(ディープリンク対応、認証による遷移制御など)は、go_router パッケージの導入を検討してみてください。


実践:簡単なTODOアプリを作ってみよう

ここまで学んだウィジェットを組み合わせて、シンプルなTODOアプリを作ってみましょう。

完成イメージと使うウィジェット

  • テキスト入力(TextField)でタスクを追加
  • リスト表示(ListView.builder)でタスク一覧を表示
  • チェックボックス(Checkbox)で完了/未完了を切り替え
  • 削除ボタン(IconButton)でタスクを削除
  • 状態管理は setState で実装

コード全文と解説

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: 'TODO App',
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const TodoPage(),
    );
  }
}

class TodoPage extends StatefulWidget {
  const TodoPage({super.key});

  @override
  State<TodoPage> createState() => _TodoPageState();
}

class _TodoPageState extends State<TodoPage> {
  final _controller = TextEditingController();
  final List<Map<String, dynamic>> _todos = [];

  void _addTodo() {
    final text = _controller.text.trim();
    if (text.isEmpty) return;

    setState(() {
      _todos.add({'title': text, 'done': false});
    });
    _controller.clear();
  }

  void _toggleTodo(int index) {
    setState(() {
      _todos[index]['done'] = !_todos[index]['done'];
    });
  }

  void _deleteTodo(int index) {
    setState(() {
      _todos.removeAt(index);
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('TODOリスト')),
      body: Column(
        children: [
          // 入力エリア
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'タスクを入力...',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (_) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: _addTodo,
                  child: const Text('追加'),
                ),
              ],
            ),
          ),

          // タスク一覧
          Expanded(
            child: _todos.isEmpty
                ? const Center(
                    child: Text(
                      'タスクがありません',
                      style: TextStyle(color: Colors.grey),
                    ),
                  )
                : ListView.builder(
                    itemCount: _todos.length,
                    itemBuilder: (context, index) {
                      final todo = _todos[index];
                      return ListTile(
                        leading: Checkbox(
                          value: todo['done'],
                          onChanged: (_) => _toggleTodo(index),
                        ),
                        title: Text(
                          todo['title'],
                          style: TextStyle(
                            decoration: todo['done']
                                ? TextDecoration.lineThrough
                                : TextDecoration.none,
                            color: todo['done']
                                ? Colors.grey
                                : Colors.black,
                          ),
                        ),
                        trailing: IconButton(
                          icon: const Icon(Icons.delete_outline),
                          onPressed: () => _deleteTodo(index),
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

コードのポイント解説:

  • 入力エリアRow + Expanded + SizedBox の組み合わせ。Expandedで入力欄が伸縮し、ボタンは固定幅を維持します
  • タスクの状態List<Map<String, dynamic>> で管理し、追加・完了切り替え・削除のたびに setState で画面を更新しています
  • 完了済みタスクは取り消し線(TextDecoration.lineThrough)とグレー表示で視覚的に区別
  • 空の状態で「タスクがありません」を表示する三項演算子は、Flutterアプリでよく使うパターンです
  • onSubmitted を指定しているため、キーボードのEnterキーでもタスクを追加できます

このTODOアプリは30行弱のロジック(_addTodo_toggleTodo_deleteTodo)で動いています。
ウィジェットの組み合わせとsetStateだけで、これだけの機能が実現できるのがFlutterの強みです。
ただし、ここにタスクの編集機能やカテゴリ分けを追加しようとすると、setStateだけでは状態の管理が一気に煩雑になってきます。
「そろそろ限界だな」と感じたタイミングが、RiverpodやBLoCといった状態管理ライブラリに進むベストな頃合いです。


まとめ

この記事では、Flutter入門として基本ウィジェットから画面遷移、状態管理まで実践コード付きで解説しました。

表示系ウィジェット(Text・Icon・Image・Container)で画面に要素を配置し、レイアウトウィジェット(Row・Column・Stack・ListView)で配置を制御する。
操作系ウィジェット(Button・TextField・Checkbox)でユーザーの入力を受け取り、setStateで画面を動的に更新する。
Navigatorで複数画面を行き来させる。

この一連の流れを押さえれば、TODOアプリのようなシンプルなアプリは自力で作れるようになります。

ここから先の学習ステップとしては、まずRiverpod等の状態管理パッケージに進むとアプリの規模に対応しやすくなります。
その後、Firebase連携(認証・データベース)やgo_router(高度な画面遷移管理)を学べば、本格的なアプリ開発の土台が整うでしょう。

最初から完璧を目指す必要はありません。
まずはこの記事のTODOアプリを動かしてみて、「ここを変えたらどうなるだろう」と試すところから始めてみてください。


参考情報源

コメント

タイトルとURLをコピーしました