はじめに
Flutter の UI は、すべて Widget で構成されています。テキスト、ボタン、画像、レイアウト、さらにはアプリ全体の設定まで、あらゆるものが Widget です。
この記事では、Flutter の基本思想である「すべてが Widget」を理解し、StatelessWidget の書き方と主要な Widget の使い方を学びます。
注意: この記事のコードは Flutter プロジェクト内で動作します。
import 'package:flutter/material.dart';を含めた完全なコードを掲載しています。実行にはflutter runを使用してください。
Flutter の基本思想:「すべてが Widget」
Flutter では、目に見える UI 要素(テキスト、ボタン、画像)だけでなく、レイアウト(パディング、中央寄せ)やスタイル(テーマ)もすべて Widget です。
Widget を組み合わせてツリー構造(Widget ツリー)を構築することで、画面を作ります。
Widget ツリーの概念
以下は典型的な Widget ツリーの構造です。
MaterialApp
└── Scaffold
├── AppBar
│ └── Text('タイトル')
└── body: Center
└── Column
├── Icon(Icons.flutter_dash)
├── Text('Hello')
└── ElevatedButton
└── Text('押してね')
親 Widget が子 Widget を内包する入れ子構造になっています。この構造がそのまま画面のレイアウトを決定します。
StatelessWidget の書き方
StatelessWidget は、状態(State)を持たない Widget です。一度構築されると、外部からのデータが変わらない限り再描画されません。
基本構造
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: 'StatelessWidget Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const GreetingPage(),
);
}
}
class GreetingPage extends StatelessWidget {
const GreetingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('挨拶ページ'),
),
body: const Center(
child: Text(
'こんにちは、Flutter!',
style: TextStyle(fontSize: 24),
),
),
);
}
}
StatelessWidget のポイント:
-
StatelessWidgetを継承する -
constコンストラクタを定義する(const MyWidget({super.key});) -
build()メソッドをオーバーライドして Widget ツリーを返す -
@overrideアノテーションを付ける
コンストラクタで引数を受け取る
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(
home: const Scaffold(
body: Center(
child: UserCard(
name: '田中太郎',
role: 'Flutter エンジニア',
),
),
),
);
}
}
class UserCard extends StatelessWidget {
final String name;
final String role;
const UserCard({
super.key,
required this.name,
required this.role,
});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(name, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(role, style: const TextStyle(fontSize: 16, color: Colors.grey)),
],
);
}
}
MaterialApp
MaterialApp は Material Design に基づいたアプリの骨格を提供するトップレベルの Widget です。
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: 'My Flutter App',
// アプリ全体のテーマ設定
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
// アプリ全体のテキストテーマ
textTheme: const TextTheme(
headlineMedium: TextStyle(fontWeight: FontWeight.bold),
),
),
// デバッグバナーを非表示にする
debugShowCheckedModeBanner: false,
// 最初に表示する画面
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MaterialApp デモ'),
),
body: Center(
child: Text(
'テーマカラーが Teal に設定されています',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
MaterialApp の主要プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
title |
String |
アプリのタイトル |
theme |
ThemeData |
ライトテーマの設定 |
darkTheme |
ThemeData? |
ダークテーマの設定 |
themeMode |
ThemeMode |
テーマモード(system / light / dark) |
home |
Widget? |
最初に表示する画面 |
debugShowCheckedModeBanner |
bool |
デバッグバナーの表示制御 |
routes |
Map<String, WidgetBuilder>? |
名前付きルートの定義 |
Scaffold
Scaffold は Material Design の画面レイアウト構造を提供する Widget です。
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(
debugShowCheckedModeBanner: false,
home: const ScaffoldDemo(),
);
}
}
class ScaffoldDemo extends StatelessWidget {
const ScaffoldDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 画面上部のアプリバー
appBar: AppBar(
title: const Text('Scaffold デモ'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
// 画面の本体
body: const Center(
child: Text('ここがメインコンテンツ'),
),
// 浮遊アクションボタン
floatingActionButton: FloatingActionButton(
onPressed: () {
// ボタンタップ時の処理
},
child: const Icon(Icons.add),
),
// 画面下部のナビゲーションバー
// ※ BottomNavigationBar のタップで選択状態を切り替えるには
// StatefulWidget + currentIndex + onTap が必要です(第10回で解説)
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'ホーム',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '検索',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'プロフィール',
),
],
),
// サイドドロワー(ハンバーガーメニュー)
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text(
'メニュー',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('ホーム'),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('設定'),
onTap: () {},
),
],
),
),
);
}
}
Scaffold の主要プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
appBar |
PreferredSizeWidget? |
画面上部のアプリバー |
body |
Widget? |
画面のメインコンテンツ |
floatingActionButton |
Widget? |
浮遊アクションボタン |
bottomNavigationBar |
Widget? |
画面下部のナビゲーション |
drawer |
Widget? |
左からスライドするドロワー |
endDrawer |
Widget? |
右からスライドするドロワー |
backgroundColor |
Color? |
背景色 |
AppBar
AppBar は画面上部に表示されるアプリケーションバーです。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AppBarDemo(),
);
}
}
class AppBarDemo extends StatelessWidget {
const AppBarDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// 左端のアイコン(通常は戻るボタンやメニューアイコン)
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
// タイトル
title: const Text('AppBar デモ'),
// タイトルを中央に配置するか
centerTitle: true,
// 背景色
backgroundColor: Colors.indigo,
// テキストとアイコンの色
foregroundColor: Colors.white,
// 右端のアクションアイコン
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.notifications),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
],
// AppBar の下に表示する Widget(タブバーなど)
// bottom: TabBar(...),
// 影の高さ
elevation: 4,
),
body: const Center(
child: Text('AppBar のデモ画面'),
),
);
}
}
Text Widget
Text は文字列を表示する最も基本的な Widget です。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: TextDemo(),
);
}
}
class TextDemo extends StatelessWidget {
const TextDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Text Widget')),
body: const Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 基本的な Text
Text('基本のテキスト'),
SizedBox(height: 16),
// フォントサイズ指定
Text(
'フォントサイズ 28',
style: TextStyle(fontSize: 28),
),
SizedBox(height: 16),
// 太字
Text(
'太字のテキスト',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 16),
// 色指定
Text(
'赤いテキスト',
style: TextStyle(color: Colors.red),
),
SizedBox(height: 16),
// 複数のスタイルを組み合わせ
Text(
'カスタムスタイル',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.deepPurple,
letterSpacing: 2.0,
decoration: TextDecoration.underline,
decorationColor: Colors.deepPurple,
),
),
SizedBox(height: 16),
// イタリック
Text(
'イタリック体',
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 18,
),
),
SizedBox(height: 16),
// テキストの折り返しと省略
Text(
'これは長いテキストです。maxLines を指定すると指定行数で切り詰められます。overflow で省略記号を表示できます。テキストが長い場合の挙動を確認してみましょう。',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16),
),
],
),
),
);
}
}
TextStyle の主要プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
fontSize |
double? |
フォントサイズ |
fontWeight |
FontWeight? |
太さ(FontWeight.bold、FontWeight.w600 等) |
color |
Color? |
文字色 |
fontStyle |
FontStyle? |
FontStyle.italic / FontStyle.normal
|
letterSpacing |
double? |
文字間隔 |
wordSpacing |
double? |
単語間隔 |
decoration |
TextDecoration? |
装飾(下線、取り消し線等) |
height |
double? |
行の高さ(倍率) |
Icon Widget
Icon はマテリアルデザインアイコンを表示する Widget です。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: IconDemo(),
);
}
}
class IconDemo extends StatelessWidget {
const IconDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Icon Widget')),
body: const Padding(
padding: EdgeInsets.all(16.0),
child: Wrap(
spacing: 24,
runSpacing: 24,
children: [
// 基本的なアイコン
Icon(Icons.home),
// サイズ指定
Icon(Icons.star, size: 48),
// 色指定
Icon(Icons.favorite, color: Colors.red, size: 48),
// 様々なアイコン
Icon(Icons.settings, size: 36),
Icon(Icons.search, size: 36),
Icon(Icons.person, size: 36),
Icon(Icons.email, size: 36),
Icon(Icons.phone, size: 36),
Icon(Icons.camera_alt, size: 36),
Icon(Icons.cloud, size: 36, color: Colors.blue),
Icon(Icons.flutter_dash, size: 48, color: Colors.cyan),
],
),
),
);
}
}
利用できるアイコンの一覧は Material Icons で確認できます。
Icons.xxxの形式で使用します。
Image Widget
Image Widget は画像を表示するために使用します。
Image.network(ネットワーク画像)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: ImageDemo(),
);
}
}
class ImageDemo extends StatelessWidget {
const ImageDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Image Widget')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// ネットワーク画像
Image.network(
'https://storage.googleapis.com/cms-storage-bucket/c823e53b3a1a7b0d36a9.png',
height: 100,
),
const SizedBox(height: 16),
// サイズとフィットを指定
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
'https://storage.googleapis.com/cms-storage-bucket/c823e53b3a1a7b0d36a9.png',
width: 200,
height: 100,
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
const Text(
'Image.network はネットワーク上の画像を読み込みます',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
),
);
}
}
Image.asset(ローカルアセット画像)
ローカル画像を使用するには、まず pubspec.yaml で画像のパスを登録します。
flutter:
uses-material-design: true
assets:
- assets/images/
プロジェクトのルートに assets/images/ ディレクトリを作成し、画像を配置します。
// Image.asset の使用例(pubspec.yaml でアセット登録が必要)
Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
fit: BoxFit.contain,
),
BoxFit の種類
| 値 | 説明 |
|---|---|
BoxFit.contain |
アスペクト比を維持して収まるように表示 |
BoxFit.cover |
アスペクト比を維持して領域を埋めるように表示 |
BoxFit.fill |
領域に合わせて引き伸ばす(歪みあり) |
BoxFit.fitWidth |
幅に合わせる |
BoxFit.fitHeight |
高さに合わせる |
BoxFit.none |
元のサイズのまま表示 |
ボタン Widget
Flutter には用途に応じた複数のボタン Widget があります。
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(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const ButtonDemo(),
);
}
}
class ButtonDemo extends StatelessWidget {
const ButtonDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ボタン Widget')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ElevatedButton: 影付きの塗りつぶしボタン
ElevatedButton(
onPressed: () {
debugPrint('ElevatedButton がタップされました');
},
child: const Text('ElevatedButton'),
),
const SizedBox(height: 16),
// TextButton: テキストのみのフラットなボタン
TextButton(
onPressed: () {
debugPrint('TextButton がタップされました');
},
child: const Text('TextButton'),
),
const SizedBox(height: 16),
// OutlinedButton: 枠線付きのボタン
OutlinedButton(
onPressed: () {
debugPrint('OutlinedButton がタップされました');
},
child: const Text('OutlinedButton'),
),
const SizedBox(height: 16),
// IconButton: アイコンのみのボタン
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.thumb_up),
onPressed: () {},
color: Colors.blue,
iconSize: 32,
),
IconButton(
icon: const Icon(Icons.thumb_down),
onPressed: () {},
color: Colors.red,
iconSize: 32,
),
],
),
const SizedBox(height: 16),
// アイコン付きボタン
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('送信する'),
),
const SizedBox(height: 16),
// スタイルをカスタマイズしたボタン
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
textStyle: const TextStyle(fontSize: 18),
),
child: const Text('カスタムスタイルボタン'),
),
const SizedBox(height: 16),
// 無効化されたボタン(onPressed を null にする)
const ElevatedButton(
onPressed: null,
child: Text('無効なボタン'),
),
],
),
),
);
}
}
ボタンの種類
| Widget | 見た目 | 用途 |
|---|---|---|
ElevatedButton |
塗りつぶし + 影 | 主要なアクション |
TextButton |
テキストのみ | 補助的なアクション |
OutlinedButton |
枠線のみ | 中程度の重要度のアクション |
IconButton |
アイコンのみ | ツールバー等のアイコンアクション |
レイアウト補助 Widget
Padding
Widget の周囲に余白を追加します。
Padding(
padding: const EdgeInsets.all(16.0), // 全方向 16
child: Text('パディング付きテキスト'),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0, // 左右 24
vertical: 8.0, // 上下 8
),
child: Text('左右と上下で異なるパディング'),
),
Padding(
padding: const EdgeInsets.only(
left: 16.0,
top: 8.0,
),
child: Text('個別指定のパディング'),
),
Center
子 Widget を親の中央に配置します。
Center(
child: Text('中央に配置されたテキスト'),
),
SizedBox
固定サイズのボックスを作成します。Widget 間のスペーサーとしてよく使われます。
Column(
children: [
Text('上のテキスト'),
SizedBox(height: 24), // 縦方向のスペース
Text('下のテキスト'),
],
),
Row(
children: [
Text('左'),
SizedBox(width: 16), // 横方向のスペース
Text('右'),
],
),
// 固定サイズのボックス
SizedBox(
width: 200,
height: 100,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.blue),
child: Center(child: Text('200x100')),
),
),
Divider
水平方向の区切り線を表示します。
Column(
children: [
Text('セクション 1'),
Divider(), // デフォルトの区切り線
Text('セクション 2'),
Divider(thickness: 2, color: Colors.red), // カスタム区切り線
Text('セクション 3'),
],
),
実践例: プロフィールカード画面の作成
ここまで学んだ Widget を組み合わせて、プロフィールカード画面を作成します。
import 'package:flutter/material.dart';
void main() {
runApp(const ProfileApp());
}
class ProfileApp extends StatelessWidget {
const ProfileApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'プロフィールカード',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const ProfilePage(),
);
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('プロフィール'),
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {},
),
],
),
body: SingleChildScrollView(
child: Column(
children: [
// ヘッダー部分
Container(
width: double.infinity,
color: Theme.of(context).colorScheme.inversePrimary,
padding: const EdgeInsets.only(bottom: 24),
child: const Column(
children: [
// アバター(アイコンで代用)
CircleAvatar(
radius: 50,
backgroundColor: Colors.white,
child: Icon(
Icons.person,
size: 60,
color: Colors.indigo,
),
),
SizedBox(height: 16),
Text(
'山田 太郎',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
'Flutter Developer',
style: TextStyle(
fontSize: 16,
color: Colors.black54,
),
),
],
),
),
// 統計情報
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_StatItem(label: '投稿', count: '128'),
_StatItem(label: 'フォロワー', count: '1.2K'),
_StatItem(label: 'フォロー中', count: '356'),
],
),
),
const Divider(),
// プロフィール情報リスト
const _ProfileInfoTile(
icon: Icons.email,
title: 'メール',
subtitle: 'taro.yamada@example.com',
),
const _ProfileInfoTile(
icon: Icons.location_on,
title: '所在地',
subtitle: '東京都渋谷区',
),
const _ProfileInfoTile(
icon: Icons.work,
title: '職業',
subtitle: 'モバイルアプリエンジニア',
),
const _ProfileInfoTile(
icon: Icons.link,
title: 'ウェブサイト',
subtitle: 'https://example.com',
),
const SizedBox(height: 24),
// 自己紹介
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'自己紹介',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Flutter と Dart が大好きなエンジニアです。'
'クロスプラットフォーム開発の魅力を日々発信しています。'
'お気軽にフォローしてください!',
style: TextStyle(
fontSize: 15,
height: 1.6,
color: Colors.black87,
),
),
],
),
),
const SizedBox(height: 24),
// アクションボタン
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.person_add),
label: const Text('フォローする'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.message),
label: const Text('メッセージ'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
],
),
),
const SizedBox(height: 32),
],
),
),
);
}
}
// 統計アイテム Widget
class _StatItem extends StatelessWidget {
final String label;
final String count;
const _StatItem({
required this.label,
required this.count,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
count,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
);
}
}
// プロフィール情報タイル Widget
class _ProfileInfoTile extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
const _ProfileInfoTile({
required this.icon,
required this.title,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon, color: Colors.indigo),
title: Text(title),
subtitle: Text(subtitle),
);
}
}
Widget ツリーの構造
上記のプロフィールカード画面の Widget ツリーは以下のようになります。
MaterialApp
└── Scaffold
├── AppBar
│ ├── Text('プロフィール')
│ └── IconButton(Icons.edit)
└── body: SingleChildScrollView
└── Column
├── Container(ヘッダー)
│ └── Column
│ ├── CircleAvatar
│ │ └── Icon(Icons.person)
│ ├── Text('山田 太郎')
│ └── Text('Flutter Developer')
├── Row(統計情報)
│ ├── _StatItem('投稿')
│ ├── _StatItem('フォロワー')
│ └── _StatItem('フォロー中')
├── Divider
├── _ProfileInfoTile(メール)
├── _ProfileInfoTile(所在地)
├── _ProfileInfoTile(職業)
├── _ProfileInfoTile(ウェブサイト)
├── Column(自己紹介)
│ ├── Text('自己紹介')
│ └── Text('Flutter と Dart が...')
└── Row(ボタン)
├── ElevatedButton('フォローする')
└── OutlinedButton('メッセージ')
このように、小さな Widget を組み合わせて大きな画面を構築するのが Flutter の基本的な考え方です。_StatItem や _ProfileInfoTile のように再利用可能な Widget を切り出すことで、コードの見通しが良くなります。
まとめ
この記事で学んだ Widget を整理します。
| カテゴリ | Widget | 用途 |
|---|---|---|
| アプリ構造 | MaterialApp |
アプリ全体の設定 |
| レイアウト | Scaffold |
画面の基本構造 |
| ナビゲーション | AppBar |
画面上部のバー |
| 表示 | Text |
テキスト表示 |
| 表示 | Icon |
アイコン表示 |
| 表示 | Image |
画像表示 |
| 操作 | ElevatedButton |
塗りつぶしボタン |
| 操作 | TextButton |
テキストボタン |
| 操作 | OutlinedButton |
枠線ボタン |
| 補助 | Padding |
余白の追加 |
| 補助 | Center |
中央寄せ |
| 補助 | SizedBox |
固定サイズ / スペーサー |
| 補助 | Divider |
区切り線 |
次の記事では、StatefulWidget を使った状態管理を学び、ユーザー操作に応じて画面を動的に変化させる方法を解説します。
参考
- Flutter 公式ドキュメント - Widget catalog
- Flutter 公式ドキュメント - Layouts in Flutter
- Flutter 公式ドキュメント - StatelessWidget class
- Flutter 公式ドキュメント - MaterialApp class
- Flutter 公式ドキュメント - Scaffold class
- Material Design Icons
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!