متتبع العادات: علّم يومًا
جزء من قسم المشاريع في رحلة Flutter على Coddy. الدرس 30 من 36.
انقر على دائرة لوضع علامة على اليوم، وانقر عليها مرة أخرى لإزالة العلامة. بيانات العادات موجودة في حالة الشاشة، لكن الدائرة مرسومة في عمق عنصر HabitCard. يجب أن تنتقل النقرة إلى الأعلى، إلى الموضع الذي توجد فيه البيانات.
هذا هو رفع الحالة إلى الأعلى باستخدام دوال الاستدعاء. الشاشة هي التي تتولى التغيير:
void _toggle(Habit habit, int day) {
setState(() {
habit.days[day] = !habit.days[day];
});
}وتمرر إلى كل بطاقة دالةً، onToggle: (day) => _toggle(habit, day). نوعها في البطاقة هو void Function(int day)، أي دالة تأخذ رقم اليوم. وتحصل كل دائرة على VoidCallback عادية، onTap: () => onToggle(i)، وتُغلّف عنصر Container الخاص بها داخل GestureDetector يستدعيها.
يبدّل !habit.days[day] قيمة bool: تتحول true إلى false والعكس، لذا تؤدي دالة واحدة مهمتي وضع العلامة وإزالتها.
التحدي
صعبحدّد الأيام:
- أضف الطريقة
_toggleإلى حالة الشاشة - أضف إلى
HabitCardالخاصيةonToggleالإلزامية من النوعvoid Function(int day)، ومرّر(day) => _toggle(habit, day) - أضف إلى
DayCircleالخاصيةVoidCallback onTapالإلزامية، ولفّContainerالخاص بها داخلGestureDetector، ومرّر() => onToggle(i)
جرّب بنفسك
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,
),
),
);
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس المشاريع
تدرّب بنفسك: مترجم Flutter عبر الإنترنت