습관 추적기: 연속 기록
Coddy Flutter 여정의 프로젝트 섹션에 포함된 레슨. 36개 중 32번째.
연속 기록은 사람들이 다시 돌아오게 만듭니다. 며칠 연속으로 이어진 기록은 지키고 싶은 무언가처럼 느껴지기 때문입니다. 여기서 연속 기록은 한 주 동안 완료한 날이 가장 길게 이어진 횟수입니다. 월요일부터 수요일까지, 그리고 금요일에 물 마시기를 완료했다면 연속 기록은 3일입니다.
연속된 횟수를 세려면 두 개의 숫자, 즉 지금까지 연속된 날 수인 run과 지금까지 확인된 가장 긴 연속 횟수인 best를 사용해 날짜를 순서대로 살펴봅니다. 완료한 날은 연속 횟수에 더하고, 미완료한 날은 0으로 초기화합니다.
int get streak {
int best = 0;
int run = 0;
for (bool day in days) {
if (day) {
run++;
if (run > best) best = run;
} else {
run = 0;
}
}
return best;
}run이 아니라 best를 반환하세요. 루프가 끝났을 때 run은 마지막 연속 구간만 나타내며, 일요일이 미완료라면 0입니다.
주간 기록 아래에 불꽃 아이콘과 함께 표시하세요. 연속 기록이 있으면 주황색으로, 없으면 테마의 outline 회색으로 표시합니다. "0일 연속 기록"은 어색하므로, 그때는 "아직 연속 기록이 없습니다"라고 표시하세요.
챌린지
어려움연속 달성 횟수를 표시하세요:
Habit에streak게터를 추가하세요- 주 아래에 12픽셀 간격을 두고
Row를 추가하세요: 크기가 20인Icons.local_fire_department(연속 달성 횟수가 0보다 크면 주황색, 그렇지 않으면 테마의outline), 4픽셀 간격, 그리고 텍스트 - 텍스트는
3 day streak(실제 횟수 표시)이며, 횟수가 0이면No streak yet입니다
직접 해보기
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.green),
),
home: const HabitScreen(),
);
}
}
const List<String> dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
class Habit {
Habit(this.name, this.days);
final String name;
final List<bool> days;
int get done => days.where((day) => day).length;
}
class HabitScreen extends StatefulWidget {
const HabitScreen({super.key});
@override
State<HabitScreen> createState() => _HabitScreenState();
}
class _HabitScreenState extends State<HabitScreen> {
final List<Habit> _habits = [
Habit('Drink water', [true, true, true, false, true, false, false]),
Habit('Read 10 pages', [true, false, true, true, false, false, false]),
Habit('Stretch', List.filled(7, false)),
];
void _toggle(Habit habit, int day) {
setState(() {
habit.days[day] = !habit.days[day];
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Habits')),
body: ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 88),
children: [
for (Habit habit in _habits)
HabitCard(
habit: habit,
onToggle: (day) => _toggle(habit, day),
),
],
),
);
}
}
class HabitCard extends StatelessWidget {
const HabitCard({super.key, required this.habit, required this.onToggle});
final Habit habit;
final void Function(int day) onToggle;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Card(
margin: const EdgeInsets.only(top: 12),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: Text(habit.name, style: textTheme.titleMedium)),
Text('${habit.done} of 7 days', style: textTheme.bodySmall),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
for (int i = 0; i < 7; i++)
DayCircle(
label: dayNames[i],
done: habit.days[i],
onTap: () => onToggle(i),
),
],
),
],
),
),
);
}
}
class DayCircle extends StatelessWidget {
const DayCircle({super.key, required this.label, required this.done, required this.onTap});
final String label;
final bool done;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: done ? colors.primary : colors.surfaceContainerHighest,
),
child: Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: done ? colors.onPrimary : colors.onSurfaceVariant,
),
),
),
);
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
프로젝트의 모든 레슨
직접 연습해 보세요: 온라인 Flutter 컴파일러