انتقالات التنقل وأفضل الممارسات
انتقالات التنقل وأفضل الممارسات
التطبيق المصقول في Flutter لا يكون صحيحاً فحسب — بل يُحسّ بأنه صحيح. الانتقالات المخصصة بين الصفحات، وتنظيم تعريفات المسارات، والتعامل المتسق مع زر الرجوع، كلها تفرق بين تطبيق يصفه المستخدمون بأنه "سلس" وآخر يصفونه بأنه "متقطع". في هذا الدرس ستتقن CustomTransitionPage، وتتعلم كيفية هيكلة المسارات لقواعد الكود الكبيرة، وتطبق أفضل ممارسات التنقل التي تمنع الأخطاء الشائعة في وقت التشغيل.
فهم CustomTransitionPage
يلف GoRouter كل وجهة في كائن صفحة. بشكل افتراضي يستخدم MaterialPage (انزلاق بأسلوب Android) أو CupertinoPage (انزلاق بأسلوب iOS). لاستبدال الرسوم المتحركة بالكامل، قدّم CustomTransitionPage كـ pageBuilder لـ GoRoute.
انتقال تلاشي مع CustomTransitionPage
GoRoute(
path: '/details/:id',
pageBuilder: (context, state) {
return CustomTransitionPage<void>(
key: state.pageKey,
child: DetailsPage(id: state.pathParameters['id']!),
transitionDuration: const Duration(milliseconds: 350),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// تلاشي للداخل عند الدفع، تلاشي للخارج عند الإزالة
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOut).animate(animation),
child: child,
);
},
);
},
),
معاملات المُنشئ الرئيسية:
- key — مرر دائماً
state.pageKeyحتى يستطيع GoRouter التمييز بين الصفحات أثناء الانتقالات. - child — ودجت الوجهة (مبني مسبقاً؛ لا يوجد استدعاء
builderهنا). - transitionDuration — يتحكم في مدة الدفع؛
reverseTransitionDurationيتحكم في مدة الإزالة بشكل منفصل. - transitionsBuilder — يستقبل أربعة معاملات:
context، وanimation(من 0 إلى 1 عند الدفع)، وsecondaryAnimation(من 0 إلى 1 عندما تُدفع صفحة جديدة فوقها)، وchild.
child في عدة ودجات انتقال. التركيب الشائع هو FadeTransition حول SlideTransition لإنتاج تأثير تلاشي وانزلاق دون أي حزم إضافية.أمثلة على انتقالي الانزلاق والتدرج
نمطان آخران ستواجههما في تطبيقات الإنتاج:
انتقالا الانزلاق للأعلى والتدرج
// انزلاق للأعلى من الأسفل (أسلوب نافذة منبثقة)
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final tween = Tween(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).chain(CurveTween(curve: Curves.easeOutCubic));
return SlideTransition(position: animation.drive(tween), child: child);
},
// تدرج من المركز (تأثير بقعة الضوء)
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: Tween<double>(begin: 0.85, end: 1.0)
.chain(CurveTween(curve: Curves.easeOut))
.animate(animation),
child: FadeTransition(
opacity: animation,
child: child,
),
);
},
تنظيم المسارات للتطبيقات الكبيرة
وضع كل GoRoute في ملف واحد يصبح صعب الإدارة بمجرد أن ينمو التطبيق إلى أكثر من اثنتي عشرة شاشة. النمط الموصى به هو تقسيم تعريفات المسارات حسب الميزة:
- أنشئ مجلد
router/تحتlib/. - كل ميزة تُظهر ثابت
List<RouteBase>(مثلauthRoutes،shopRoutes). - ملف
app_router.dartعلى المستوى الأعلى يدمجها في نسخةGoRouterواحدة. - استخدم المسارات المسماة (المعامل
name:فيGoRoute) وتنقل باستخدامcontext.goNamed('routeName')حتى لا تكسر نقاط الاستدعاء عند تغيير المسارات.
AppRoutes مجردة واحدة بحقول const String ساكنة. هذا يزيل السلاسل السحرية من كل استدعاء تنقل ويجعل إعادة تسمية مسار تعديلاً من سطر واحد.معالجة الأخطاء في التنقل
يوفر GoRouter خطافَين لأخطاء التنقل:
- errorBuilder — يعرض ودجت عندما لا يطابق أي مسار المسار المطلوب (على غرار خطأ 404).
- redirect — يعترض كل حدث تنقل؛ ألقِ
GoExceptionأو أعد مسار التحويل لحماية المسارات (حراسة المصادقة، علامات الميزات).
context.go() أو Navigator.push() داخل طريقة build() للودجت. يجب أن يكون التنقل مُشغَّلاً بواسطة أحداث المستخدم (callbacks) أو callbacks ما بعد الإطار (WidgetsBinding.instance.addPostFrameCallback). التنقل أثناء البناء يسبب خطأ "setState() called during build" ويمكن أن يعطّل التطبيق.سلوك زر الرجوع في Android
يتكامل GoRouter مع زر الرجوع في Android تلقائياً عبر ودجت Router. ومع ذلك توجد حالات يجب فيها التعامل معه صراحةً:
- لف الشاشة بـ
PopScope(Flutter 3.16+؛ يحل محلWillPopScopeالمهمل) لاعتراض ضغطات الرجوع. - اضبط
canPop: falseلمنع الإزالة التلقائية (مثلاً شاشات تأكيد الدفع). - استخدم callback يُدعى
onPopInvokedWithResultلعرض مربع حوار "هل تريد الخروج؟" قبل السماح بالإزالة. - على الويب وسطح المكتب، تذكر أن زر الرجوع في المتصفح/نظام التشغيل يطلق نفس إزالة الراوتر — اختبر على جميع المنصات.
ملخص أفضل الممارسات
- مرر دائماً
state.pageKeyإلىCustomTransitionPageلتجنب أخطاء المفاتيح. - استخدم المسارات المسماة وفئة ثوابت
AppRoutesمركزية. - قسّم أشجار المسارات الكبيرة إلى قوائم لكل ميزة وادمجها على مستوى التطبيق.
- قدّم
errorBuilderحتى لا تُظهر المسارات غير المتطابقة شاشة فارغة أبداً. - استخدم
PopScope(وليسWillPopScope) لاعتراض زر الرجوع. - احتفظ بمدد الانتقال بين 200–400 مللي ثانية.
- لا تتنقل داخل
build()؛ استخدم callbacks أوaddPostFrameCallback.
CustomTransitionPage تحكماً على مستوى البكسل في كيفية ظهور الشاشات واختفائها، بينما تُبقي هياكل المسارات المقسمة حسب الميزة والمسارات المسماة رسماً بيانياً كبيراً للتنقل قابلاً للصيانة وآمناً عند إعادة الهيكلة.