Theo dõi thói quen: Chuỗi ngày liên tiếp
Thuộc phần Dự án trong hành trình Flutter của Coddy. Bài học 32 trên 36.
Streak khiến mọi người muốn quay lại: số ngày liên tiếp tạo cảm giác như một điều cần giữ gìn. Ở đây, streak là số ngày đã hoàn thành liên tiếp dài nhất trong tuần. Uống nước, hoàn thành từ thứ Hai đến thứ Tư và vào thứ Sáu, có streak là 3.
Để đếm số ngày liên tiếp, hãy lần lượt xét từng ngày bằng hai số: run, số ngày liên tiếp tính đến thời điểm hiện tại, và best, số ngày liên tiếp dài nhất đã ghi nhận. Ngày đã hoàn thành làm tăng run; ngày chưa hoàn thành đặt lại giá trị này thành 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;
}Trả về best, không phải run: khi vòng lặp kết thúc, run chỉ là chuỗi ngày liên tiếp cuối cùng, bằng 0 nếu Chủ nhật chưa hoàn thành.
Hiển thị streak bên dưới tuần cùng biểu tượng ngọn lửa, màu cam khi có streak và màu xám outline theo chủ đề khi không có streak. Cụm “streak 0 ngày” nghe không tự nhiên, nên lúc đó hãy ghi “Chưa có streak”.
Thử thách
KhóHiển thị streak:
- thêm getter
streakvàoHabit - bên dưới tuần, sau 12 pixel, thêm một
Row:Icons.local_fire_departmentở kích thước 20 (màu cam khi streak lớn hơn 0, nếu không thì dùngoutlinecủa chủ đề), 4 pixel và phần văn bản - phần văn bản là
3 day streak(với số thực tế), hoặcNo streak yetkhi giá trị là 0
Tự làm thử
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,
),
),
),
);
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Dự án
1Dự án: Danh sách việc cần làm
Danh sách việc cần làm: Danh sách công việcDanh sách việc cần làm: Thêm công việc4Dự án: Theo dõi thói quen
Theo dõi thói quen: Thói quen của bạnTheo dõi thói quen: TuầnTự luyện tập: Trình biên dịch Flutter online