【TaskCard】タスクをLINEの吹き出し風UIで表示する
このページでやること
このページでは、タスク1件分を表示する TaskCard を、LINEの吹き出し風UIに整えます。
前のページでは、タスクの status を使って、未対応・進行中・完了に分けました。
今回は、タスク一覧画面で見やすくなるように、タスクをカードではなく「吹き出し」のように表示します。
Firestoreからタスクを取得
↓
TaskCardに渡す
↓
LINEの吹き出し風に表示する
今日のゴール
タスクが次のように表示されることがゴールです。
未対応 優先度:中
ログイン画面を作る
メールアドレスとパスワードの入力欄を作る
担当:test@example.com
見た目は、白い吹き出しのような箱にします。
薄いグレー背景
↓
白い吹き出し
↓
ステータス・優先度・タスク名・説明・担当者を表示
このページで出てくる単語
| 単語 | 一言説明 |
|---|---|
TaskCard | タスク1件分を表示する部品 |
| 吹き出しUI | チャットのメッセージのような見た目 |
Container | 箱を作るWidget |
BoxDecoration | 背景色・角丸・枠線などを設定するもの |
Column | 縦に並べるWidget |
Row | 横に並べるWidget |
status | タスクの進行状況 |
priority | タスクの優先度 |
Widget とは、Flutterの画面部品のことです。
npmや環境変数はこのページで必要?
このページでは、npmは使いません。
環境変数も設定しません。
このページでやることは、TaskCard の見た目を整えるだけです。
main.dartを開く
↓
TaskCardを探す
↓
吹き出し風UIに変更する
↓
保存する
↓
flutter runで確認する
Step 1:main.dartを開く
次のファイルを開きます。
lib/main.dart
ターミナルから開く場合は、次を実行します。
code lib/main.dart
code が使えない場合は、VS Codeの左側から lib/main.dart を開いてください。
Step 2:TaskCardを探す
main.dart の中で、次を検索します。
class TaskCard
VS Codeなら、次で検索できます。
command + F
この TaskCard が、タスク1件分の見た目を作っている場所です。
Step 3:TaskCardの役割を確認する
TaskCard は、次の値を受け取ります。
final String title;
final String description;
final String status;
final String priority;
final String assigneeEmail;
それぞれの意味は次の通りです。
| 値 | 画面に出す内容 |
|---|---|
title | タスク名 |
description | タスク説明 |
status | 未対応・進行中・完了 |
priority | 優先度 |
assigneeEmail | 担当者メール |
Step 4:TaskCardを吹き出し風に差し替える
今ある TaskCard を、次のコードに差し替えてください。
class TaskCard extends StatelessWidget {
const TaskCard({
super.key,
required this.title,
required this.description,
required this.status,
required this.priority,
required this.assigneeEmail,
});
final String title;
final String description;
final String status;
final String priority;
final String assigneeEmail;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.centerLeft,
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(18),
bottomLeft: Radius.circular(18),
bottomRight: Radius.circular(18),
),
border: Border.all(color: AppColors.border),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_Label(text: statusLabel(status)),
_Label(text: priorityLabel(priority)),
],
),
const SizedBox(height: 10),
Text(
title,
style: const TextStyle(
color: AppColors.text,
fontSize: 16,
fontWeight: FontWeight.w900,
),
),
if (description.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
description,
style: const TextStyle(
color: AppColors.subText,
height: 1.5,
fontWeight: FontWeight.w600,
),
),
],
if (assigneeEmail.isNotEmpty) ...[
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: AppColors.bg,
borderRadius: BorderRadius.circular(999),
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
'担当:$assigneeEmail',
style: const TextStyle(
color: AppColors.subText,
fontSize: 12,
fontWeight: FontWeight.w800,
),
),
),
],
],
),
),
);
}
String statusLabel(String value) {
switch (value) {
case 'doing':
return '進行中';
case 'done':
return '完了';
case 'todo':
default:
return '未対応';
}
}
String priorityLabel(String value) {
switch (value) {
case 'high':
return '優先度:高';
case 'low':
return '優先度:低';
case 'normal':
default:
return '優先度:中';
}
}
}
これで、タスクが白い吹き出し風に表示されます。
Step 5:吹き出し風に見せている部分
吹き出し風に見せている中心は、ここです。
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(18),
bottomLeft: Radius.circular(18),
bottomRight: Radius.circular(18),
),
左上だけ少し角丸を小さくしています。
これによって、普通のカードではなく、チャットの吹き出しに近い雰囲気になります。
左上:少し角を小さくする
↓
吹き出しっぽく見える
Step 6:背景色と影を見る
この部分です。
color: Colors.white,
border: Border.all(color: AppColors.border),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
意味はこうです。
| コード | 役割 |
|---|---|
color: Colors.white | 吹き出しを白にする |
border | 薄い線をつける |
boxShadow | ほんの少し影をつける |
背景が AppColors.chatBg の薄いグレーなので、白い吹き出しが見やすくなります。
Step 7:ラベルを横並びにする
ステータスと優先度は、次のように表示します。
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_Label(text: statusLabel(status)),
_Label(text: priorityLabel(priority)),
],
),
Wrap は、横に並べて、入りきらないときは自動で折り返すWidgetです。
スマホの画面が狭いときでも崩れにくくなります。
未対応 優先度:中
のように表示されます。
Step 8:タスク名を目立たせる
タスク名は、この部分です。
Text(
title,
style: const TextStyle(
color: AppColors.text,
fontSize: 16,
fontWeight: FontWeight.w900,
),
),
fontWeight: FontWeight.w900 は、文字をかなり太くする設定です。
タスク名は重要なので、太字で見やすくします。
Step 9:説明文は空なら表示しない
説明文は、このように書いています。
if (description.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
description,
style: const TextStyle(
color: AppColors.subText,
height: 1.5,
fontWeight: FontWeight.w600,
),
),
],
description.isNotEmpty は、説明文が空ではない場合という意味です。
説明が空のタスクでは、余計な空白を出しません。
説明あり → 表示する
説明なし → 表示しない
Step 10:担当者メールを小さく表示する
担当者は、この部分で表示しています。
if (assigneeEmail.isNotEmpty) ...[
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: AppColors.bg,
borderRadius: BorderRadius.circular(999),
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
'担当:$assigneeEmail',
style: const TextStyle(
color: AppColors.subText,
fontSize: 12,
fontWeight: FontWeight.w800,
),
),
),
],
担当者メールも、空なら表示しません。
入力されている場合だけ、小さなラベルのように出します。
Step 11:_Labelを確認する
TaskCard の中で _Label を使っています。
まだない場合は、次のコードを追加します。
class _Label extends StatelessWidget {
const _Label({
required this.text,
});
final String text;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.bg,
borderRadius: BorderRadius.circular(999),
border: Border.all(color: AppColors.border),
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
child: Text(
text,
style: const TextStyle(
color: AppColors.subText,
fontSize: 12,
fontWeight: FontWeight.w800,
),
),
);
}
}
_Label は、ステータスや優先度を小さく表示するための部品です。
Step 12:TaskListPageの背景を確認する
TaskListPage の Scaffold が、次のようになっているか確認します。
return Scaffold(
backgroundColor: AppColors.chatBg,
appBar: AppBar(
title: Text(teamName),
),
body: ...
);
大事なのは、ここです。
backgroundColor: AppColors.chatBg,
薄いグレー背景にすることで、白い吹き出しが見やすくなります。
Step 13:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 14:実行する
ターミナルで実行します。
flutter run
すでに起動している場合は、ターミナルで r を押します。
r
うまく反映されない場合は、R でホットリスタートします。
R
Step 15:画面を確認する
チームをタップして、タスク一覧画面を開きます。
タスクがある場合、次のような白い吹き出しで表示されればOKです。
未対応 優先度:中
ログイン画面を作る
メールとパスワードの入力欄を作る
担当:test@example.com
まだタスクがない場合は、Firebase Consoleでテスト用タスクを手動作成すると確認できます。
Step 16:Firebase Consoleでテストタスクを作る
Firebase Consoleを開きます。
https://console.firebase.google.com/
次の場所を開きます。
Firestore Database
↓
teams
↓
作成したteamId
↓
tasks
テスト用ドキュメントを1つ作ります。
title: ログイン画面を作る
description: メールとパスワードの入力欄を作る
status: todo
priority: normal
assigneeEmail: test@example.com
assigneeId:
createdBy: 自分のuid
アプリに戻って、吹き出し風に表示されれば成功です。
1か所だけ変更してみる
吹き出しの角丸を少し大きくしてみます。
この部分を探します。
topRight: Radius.circular(18),
bottomLeft: Radius.circular(18),
bottomRight: Radius.circular(18),
次のように変えます。
topRight: Radius.circular(24),
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
保存して、ホットリロードします。
r
吹き出しの角が丸くなれば成功です。
確認できたら、好みの数値に戻してOKです。
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
TaskCard isn't defined | TaskCardがない | class TaskCard を追加する |
_Label isn't defined | _Labelがない | _Label を追加する |
withValues でエラー | Flutterのバージョン差 | withOpacity(0.04) に変える |
| タスクが表示されない | tasksにデータがない | テストタスクを作る |
| 吹き出しに見えない | 背景が白のまま | TaskListPage を AppColors.chatBg にする |
| 画面が変わらない | 保存していない | command + S |
| ホットリロードで変わらない | 状態が残っている | R でホットリスタート |
withValues** でエラーが出る場合**
この部分でエラーが出ることがあります。
color: Colors.black.withValues(alpha: 0.04),
その場合は、次のように変更してください。
color: Colors.black.withOpacity(0.04),
どちらも、黒色を少しだけ透明にするための書き方です。
吹き出しに見えないとき
TaskListPage の背景を確認してください。
これは少し見えにくいです。
backgroundColor: AppColors.bg,
LINE風にするなら、こちらがおすすめです。
backgroundColor: AppColors.chatBg,
背景が薄いグレーになり、白い吹き出しが見やすくなります。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. TaskCardを吹き出し風にする
return Align(
alignment: Alignment.centerLeft,
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(18),
bottomLeft: Radius.circular(18),
bottomRight: Radius.circular(18),
),
border: Border.all(color: AppColors.border),
),
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_Label(text: statusLabel(status)),
_Label(text: priorityLabel(priority)),
],
),
const SizedBox(height: 10),
Text(title),
],
),
),
);
2. 背景を薄いグレーにする
backgroundColor: AppColors.chatBg,
3. 保存して実行
flutter run
チェックリスト
□ main.dartを開いた
□ TaskCardを探した
□ TaskCardを吹き出し風に変更した
□ Containerを使った
□ 背景色を白にした
□ 角丸を設定した
□ statusLabelを表示した
□ priorityLabelを表示した
□ titleを表示した
□ descriptionがある時だけ表示した
□ assigneeEmailがある時だけ表示した
□ _Labelを確認した
□ TaskListPageの背景をAppColors.chatBgにした
□ 保存した
□ flutter runで確認した
ミニ確認問題
Q1. TaskCardは何のために作りますか?
回答
タスク1件分を見やすく表示するためです。
今回は、LINEの吹き出し風UIで表示します。
Q2. 吹き出し風に見せるために使っている主な設定は何ですか?
回答
Container、BoxDecoration、borderRadius、白背景、薄い影です。
Q3. 説明文が空のときはどうしますか?
回答
表示しません。
if (description.isNotEmpty) で、説明文があるときだけ表示します。
Q4. このページでnpmや環境変数は必要ですか?
回答
必要ありません。
このページでは、TaskCard の見た目を整えるだけです。
このページのまとめ
TaskCardはタスク1件分を表示する部品。- LINE風にするため、薄いグレー背景に白い吹き出しを置く。
borderRadiusを調整して、吹き出しのような形にする。statusとpriorityは小さなラベルで表示する。titleは太字で見やすく表示する。descriptionは空でなければ表示する。assigneeEmailも空でなければ表示する。- このページではnpmや環境変数は不要。
次のページでやること
次のページでは、タスク作成UIを作ります。
右下の + ボタンからボトムシートを開き、タスク名・説明・優先度・担当者メールを入力できるようにします。
