Theo dõi thói quen: Đánh dấu một ngày
Thuộc phần Dự án trong hành trình Flutter của Coddy. Bài học 30 trên 36.
Chạm vào một hình tròn để đánh dấu ngày đó, chạm lại để bỏ đánh dấu. Dữ liệu thói quen nằm trong trạng thái của màn hình, nhưng hình tròn được vẽ sâu bên trong HabitCard. Thao tác chạm phải truyền lên nơi lưu dữ liệu.
Đó là cách đưa trạng thái lên trên bằng callback. Màn hình quản lý thay đổi:
void _toggle(Habit habit, int day) {
setState(() {
habit.days[day] = !habit.days[day];
});
}Màn hình truyền cho mỗi thẻ một hàm, onToggle: (day) => _toggle(habit, day). Kiểu của hàm này trong thẻ là void Function(int day), một hàm nhận số ngày. Mỗi hình tròn nhận một VoidCallback đơn giản, onTap: () => onToggle(i), và bọc Container của nó trong một GestureDetector để gọi hàm đó.
!habit.days[day] đảo giá trị bool: true thành false và ngược lại, nên một hàm vừa đánh dấu vừa bỏ đánh dấu.
Thử thách
KhóĐánh dấu các ngày:
- thêm phương thức
_togglevào trạng thái của màn hình - thêm tham số bắt buộc
onTogglecó kiểuvoid Function(int day)choHabitCard, rồi truyền(day) => _toggle(habit, day) - thêm tham số bắt buộc
VoidCallback onTapchoDayCircle, bọcContainerbằngGestureDetector, rồi truyền() => onToggle(i)
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;
}
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)),
];
@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),
],
),
);
}
}
class HabitCard extends StatelessWidget {
const HabitCard({super.key, required this.habit});
final Habit habit;
@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: [
Text(habit.name, style: textTheme.titleMedium),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
for (int i = 0; i < 7; i++) DayCircle(label: dayNames[i], done: habit.days[i]),
],
),
],
),
),
);
}
}
class DayCircle extends StatelessWidget {
const DayCircle({super.key, required this.label, required this.done});
final String label;
final bool done;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return 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