【共通UI】AppColors・AppCard・AppTextField・ErrorBoxを作る
このページでやること
このページでは、アプリの中で何度も使うUI部品を作ります。
UIとは、ボタン、入力欄、カード、エラー表示など、ユーザーが見る画面部品のことです。
今回作る共通UIは、次の4つです。
| 部品 | 一言説明 |
|---|---|
AppColors | アプリで使う色をまとめる |
AppCard | 白いカード型の箱を作る |
AppTextField | 入力欄の見た目を統一する |
ErrorBox | エラーメッセージを赤く表示する |
このページでは、npmや環境変数の設定は使いません。
やることは、main.dart に共通部品のコードを追加して、保存して、画面で確認するだけです。
今日のゴール
完成すると、次のように部品を短く使えるようになります。
AppCard(
child: Text('プロフィール作成'),
)
AppTextField(
controller: emailController,
label: 'メールアドレス',
)
ErrorBox(
message: 'ログインに失敗しました。',
)
同じデザインを何度も書かなくてよくなります。
作業の流れ
1. lib/main.dart を開く
↓
2. AppColors を確認する
↓
3. AppCard を追加する
↓
4. AppTextField を追加する
↓
5. ErrorBox を追加する
↓
6. 保存する
↓
7. flutter run で確認する
完成コードを使っている場合は、すでに入っています。
このページでは、「何のためにあるのか」を確認しながら進めます。
Step 1:main.dartを開く
Flutterプロジェクトの中で、次のファイルを開きます。
lib/main.dart
ターミナルから開く場合は、次を実行します。
code lib/main.dart
code が使えない場合は、VS Codeの左側から lib/main.dart を開いてください。
Step 2:AppColorsを確認する
まず、色をまとめる AppColors を確認します。
AppColors は、アプリ全体で使う色の一覧です。
class AppColors {
static const bg = Color(0xFFF3F4F6);
static const chatBg = Color(0xFFECEFF1);
static const card = Colors.white;
static const lineGreen = Color(0xFF06C755);
static const lineGreenDark = Color(0xFF00B900);
static const text = Color(0xFF111111);
static const subText = Color(0xFF8A8A8A);
static const border = Color(0xFFE5E5E5);
static const danger = Color(0xFFE53935);
static const success = Color(0xFF06C755);
static const warning = Color(0xFFFFB300);
static const primary = lineGreen;
}
Color は、色を表すFlutterの型です。
型とは、データの種類のことです。
0xFF06C755 のような文字は、色を数字で表したものです。
ここでは、LINE風に見えるように、緑、白、薄いグレーを中心にしています。
AppColorsを作る理由
色を直接あちこちに書くと、あとで変更が大変です。
たとえば、緑を変えたいときに、コードの中を全部探す必要があります。
Color(0xFF06C755)
でも、AppColors.lineGreen にしておけば、色の変更は1か所だけで済みます。
AppColors.lineGreen
共通化とは、同じものを1か所にまとめて何度も使えるようにすることです。
Step 3:AppCardを追加する
次に、白いカードUIを作ります。
カードUIとは、情報を入れる白い箱のような見た目です。
main.dart の下の方に、次のコードを追加します。
class AppCard extends StatelessWidget {
const AppCard({
super.key,
required this.child,
this.onTap,
});
final Widget child;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final card = Container(
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border),
),
padding: const EdgeInsets.all(14),
child: child,
);
if (onTap == null) {
return card;
}
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: card,
),
);
}
}
StatelessWidget は、状態を持たない画面部品のことです。
状態とは、画面の中で変化するデータのことです。
Widget は、Flutterの画面部品のことです。
child は、カードの中に入れる部品です。
AppCardの使い方
たとえば、ログイン画面ではこのように使えます。
AppCard(
child: Column(
children: [
Text('WorkBoard'),
Text('チームの会話のように、タスクを共有する'),
],
),
)
Column は、部品を縦に並べるWidgetです。
AppCard を使うと、白背景、角丸、薄い線、余白を毎回書かなくて済みます。
Step 4:AppTextFieldを追加する
次に、入力欄を共通化します。
入力欄とは、メールアドレスやパスワードを入力する場所のことです。
main.dart の下の方に、次のコードを追加します。
class AppTextField extends StatelessWidget {
const AppTextField({
super.key,
required this.controller,
required this.label,
this.obscureText = false,
this.keyboardType,
this.maxLines = 1,
});
final TextEditingController controller;
final String label;
final bool obscureText;
final TextInputType? keyboardType;
final int maxLines;
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
maxLines: obscureText ? 1 : maxLines,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
),
);
}
}
TextEditingController は、入力された文字を管理するための道具です。
label は、入力欄に表示する名前です。
obscureText は、パスワードのように文字を隠す設定です。
keyboardType は、メール入力用など、キーボードの種類を指定する設定です。
AppTextFieldの使い方
メールアドレス入力欄は、こう書けます。
AppTextField(
controller: emailController,
label: 'メールアドレス',
keyboardType: TextInputType.emailAddress,
)
パスワード入力欄は、こう書けます。
AppTextField(
controller: passwordController,
label: 'パスワード',
obscureText: true,
)
obscureText: true にすると、入力した文字が見えにくくなります。
Step 5:ErrorBoxを追加する
次に、エラー表示用の部品を作ります。
エラーとは、ログイン失敗や保存失敗など、処理がうまくいかなかった状態のことです。
main.dart の下の方に、次のコードを追加します。
class ErrorBox extends StatelessWidget {
const ErrorBox({
super.key,
required this.message,
});
final String message;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.danger.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColors.danger.withValues(alpha: 0.22),
),
),
padding: const EdgeInsets.all(12),
child: Text(
message,
style: const TextStyle(
color: AppColors.danger,
fontWeight: FontWeight.w700,
),
),
);
}
}
message は、表示するエラー文です。
AppColors.danger は、削除やエラーに使う赤色です。
alpha は、透明度のことです。
ErrorBoxの使い方
ログイン失敗時などに、次のように使います。
ErrorBox(
message: 'ログインに失敗しました。',
)
完成コードでは、エラーがあるときだけ表示します。
if (errorText != null) ...[
const SizedBox(height: 12),
ErrorBox(message: errorText!),
]
if は、条件に合うときだけ処理する書き方です。
errorText != null は、エラー文がある場合という意味です。
null は、値がない状態のことです。
ここまでの完成コード
このページで必要な共通UIは、次の4つです。
class AppColors {
static const bg = Color(0xFFF3F4F6);
static const chatBg = Color(0xFFECEFF1);
static const card = Colors.white;
static const lineGreen = Color(0xFF06C755);
static const lineGreenDark = Color(0xFF00B900);
static const text = Color(0xFF111111);
static const subText = Color(0xFF8A8A8A);
static const border = Color(0xFFE5E5E5);
static const danger = Color(0xFFE53935);
static const success = Color(0xFF06C755);
static const warning = Color(0xFFFFB300);
static const primary = lineGreen;
}
class AppCard extends StatelessWidget {
const AppCard({
super.key,
required this.child,
this.onTap,
});
final Widget child;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final card = Container(
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border),
),
padding: const EdgeInsets.all(14),
child: child,
);
if (onTap == null) {
return card;
}
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: card,
),
);
}
}
class AppTextField extends StatelessWidget {
const AppTextField({
super.key,
required this.controller,
required this.label,
this.obscureText = false,
this.keyboardType,
this.maxLines = 1,
});
final TextEditingController controller;
final String label;
final bool obscureText;
final TextInputType? keyboardType;
final int maxLines;
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
maxLines: obscureText ? 1 : maxLines,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
),
);
}
}
class ErrorBox extends StatelessWidget {
const ErrorBox({
super.key,
required this.message,
});
final String message;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.danger.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColors.danger.withValues(alpha: 0.22),
),
),
padding: const EdgeInsets.all(12),
child: Text(
message,
style: const TextStyle(
color: AppColors.danger,
fontWeight: FontWeight.w700,
),
),
);
}
}
Step 6:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 7:実行する
ターミナルで実行します。
flutter run
すでにアプリが起動している場合は、ターミナルで r を押します。
r
r は、ホットリロードです。
ホットリロードとは、アプリを起動したまま画面の変更を反映する機能です。
画面で確認すること
ログイン画面を見ます。
次のようになっていればOKです。
ログインフォームが白いカードの中に入っている
入力欄の見た目がそろっている
エラーが出たときに赤い箱で表示される
まだエラー表示が見えなくても大丈夫です。
ログインに失敗したときなどに表示されます。
1か所だけ変更してみる
試しに、カードの角丸を変えてみます。
AppCard のこの行を探します。
borderRadius: BorderRadius.circular(14),
数字を少し大きくします。
borderRadius: BorderRadius.circular(24),
保存して、ホットリロードします。
r
カードの角が丸くなれば成功です。
確認できたら、元に戻しておきます。
borderRadius: BorderRadius.circular(14),
このように、共通UIを作っておくと、1か所変えるだけで全体の見た目を調整できます。
npmや環境変数はこのページで必要?
このページでは、npmは使いません。
npmとは、Node.jsのパッケージ管理ツールです。
環境変数も使いません。
環境変数とは、パソコン全体で使う設定値のことです。
今回やることは、これだけです。
main.dartを開く
↓
共通UIのコードを追加する
↓
保存する
↓
flutter runで見る
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
Color isn't defined | FlutterのMaterialを読み込んでいない | import 'package:flutter/material.dart'; を確認 |
StatelessWidget isn't defined | Materialのimport漏れ | material.dart を読み込む |
AppColors isn't defined | AppColorsより先に使っている、または未定義 | AppColorsを追加する |
TextEditingController isn't defined | Materialのimport漏れ | material.dart を読み込む |
| 画面が変わらない | 保存していない | command + S |
| それでも変わらない | ホットリロードしていない | ターミナルで r |
withValues** でエラーが出る場合**
Flutterのバージョンによっては、次の部分でエラーが出ることがあります。
AppColors.danger.withValues(alpha: 0.08)
その場合は、次のように書き換えてください。
AppColors.danger.withOpacity(0.08)
こちらも同じく透明度を指定する書き方です。
ただし、新しいFlutterでは withValues が使えます。
完成コードでエラーが出ていなければ、そのままでOKです。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. lib/main.dart を開く
2. AppColors を確認する
3. AppCard を追加する
4. AppTextField を追加する
5. ErrorBox を追加する
6. 保存する
7. flutter run
ターミナルで実行:
flutter run
起動中なら:
r
チェックリスト
□ AppColorsを作った
□ AppCardを作った
□ AppTextFieldを作った
□ ErrorBoxを作った
□ 保存した
□ flutter runで起動した
□ ログイン画面のカードを確認した
□ 入力欄の見た目を確認した
□ エラー表示用の部品を確認した
ミニ確認問題
Q1. AppColorsは何のために作りますか?
回答
アプリで使う色を1か所にまとめるためです。
色を変更したいときに、1か所直すだけで済みます。
Q2. AppCardは何のために作りますか?
回答
白いカード型のUIを何度も使えるようにするためです。
ログイン画面、新規登録画面、メンバー一覧などで使えます。
Q3. AppTextFieldは何のために作りますか?
回答
入力欄の見た目と使い方を統一するためです。
メールアドレス、パスワード、名前、所属などの入力欄で使います。
Q4. ErrorBoxは何のために作りますか?
回答
ログイン失敗や保存失敗などのエラーを、見やすく表示するためです。
このページのまとめ
- 共通UIを作ると、同じ見た目を何度も使える。
AppColorsは色をまとめる部品。AppCardは白いカードUIを作る部品。AppTextFieldは入力欄を統一する部品。ErrorBoxはエラーを赤く表示する部品。- このページではnpmや環境変数は不要。
- コードを追加したら、保存して、画面の変化を見る。
- 共通UIを作ると、あとからデザイン変更が楽になる。
次のページでやること
次のページでは、認証分岐を作ります。
AuthGate を使って、ログインしている人にはチーム一覧画面、ログインしていない人にはログイン画面を表示します。
