1. مقدمة
إنّ الرسوم المتحرّكة هي طريقة رائعة لتحسين تجربة المستخدم في تطبيقك، ونقل معلومات مهمة إليه، وجعل تطبيقك أكثر رونقًا وسهولة في الاستخدام.
نظرة عامة على إطار عمل الرسوم المتحركة في Flutter
يعرض Flutter تأثيرات الرسوم المتحركة من خلال إعادة إنشاء جزء من شجرة التطبيقات المصغّرة في كل إطار. وتوفّر هذه الواجهة تأثيرات مُعدّة مسبقًا وواجهات برمجة تطبيقات أخرى لتسهيل إنشاء الصور المتحركة وتركيبها.
- الصور المتحركة الضمنية هي تأثيرات صور متحركة مُنشأة مسبقًا تعمل على تشغيل الصورة المتحركة بالكامل تلقائيًا. عندما تتغيّر قيمة الهدف للحركة، يتم تشغيل الحركة من القيمة الحالية إلى القيمة المستهدَفة، وعرض كل قيمة بينهما لكي تظهر الحركة السلسة للتطبيق المصغّر. تشمل أمثلة الصور المتحركة الضمنية
AnimatedSize
وAnimatedScale
وAnimatedPositioned
. - الرسوم المتحركة الواضحة هي أيضًا تأثيرات رسوم متحركة مُعدّة مسبقًا، ولكنها تتطلّب عنصرًا
Animation
لكي تعمل. تشمل الأمثلةSizeTransition
أوScaleTransition
أوPositionedTransition
. - الحركة هي فئة تمثّل حركة معروضة أو متوقفة، وتتألّف من قيمة تمثّل القيمة المستهدَفة التي يتم تشغيل الحركة إليها، والحالة التي تمثّل القيمة الحالية التي تعرضها الحركة على الشاشة في أي وقت. وهي فئة فرعية من
Listenable
، وتُعلم المستمعين بها عند تغيير الحالة أثناء تشغيل الصورة المتحركة. - AnimationController هي طريقة لإنشاء صورة متحركة والتحكّم في حالتها. ويمكن استخدام طرقه، مثل
forward()
وreset()
وstop()
وrepeat()
للتحكّم في الصورة المتحركة بدون الحاجة إلى تحديد تأثير الصورة المتحركة المعروض، مثل الحجم أو المقياس أو الموضع. - تُستخدَم القيم البينية لإدراج قيم بين قيمة البداية والنهاية، ويمكن أن تمثّل أي نوع، مثل القيمة المزدوجة أو
Offset
أوColor
. - تُستخدَم الخطوط المنحنية لتعديل معدّل تغيُّر مَعلمة معيّنة بمرور الوقت. عند تشغيل صورة متحركة، من الشائع تطبيق منحنى التخفيف لجعل معدّل التغيير أسرع أو أبطأ في بداية الصورة المتحركة أو نهايتها. تأخذ المنحنيات قيمة إدخال تتراوح بين 0.0 و1.0 وتُعرِض قيمة ناتجة تتراوح بين 0.0 و1.0.
التطبيق الذي ستصممه
في هذا الدليل التعليمي حول البرمجة، ستنشئ لعبة اختبار تتضمّن أسئلة خيارات متعدّدة وتوفّر تأثيرات وتقنيات متحركة متنوعة.
ستتعرّف على كيفية إجراء ما يلي:
- إنشاء تطبيق مصغّر يغيّر حجمه ولونه بشكل متحرك
- إنشاء تأثير قلب بطاقة ثلاثي الأبعاد
- استخدام تأثيرات رسوم متحركة رائعة ومصمّمة مسبقًا من حزمة الرسوم المتحركة
- إضافة إيماءة إظهار شاشة الرجوع المتاحة في أحدث إصدار من Android
ما ستتعرّف عليه
في هذا الدليل التعليمي للترميز، ستتعرّف على ما يلي:
- كيفية استخدام التأثيرات المتحرّكة الضمنية لإنشاء صور متحركة رائعة بدون الحاجة إلى الكثير من الرموز البرمجية
- كيفية استخدام التأثيرات المتحركة الواضحة لضبط تأثيراتك الخاصة باستخدام التطبيقات المصغّرة المتحركة المُنشأة مسبقًا، مثل
AnimatedSwitcher
أوAnimationController
- كيفية استخدام
AnimationController
لتحديد التطبيق المصغّر الذي يعرض تأثيرًا ثلاثي الأبعاد - كيفية استخدام حزمة
animations
لعرض تأثيرات رسوم متحركة رائعة باستخدام الحد الأدنى من الإعدادات
المتطلبات
- حزمة تطوير البرامج (SDK) من Flutter
- بيئة تطوير متكاملة، مثل VSCode أو Android Studio / IntelliJ
2. إعداد بيئة تطوير Flutter
تحتاج إلى برنامجَين لإكمال هذا الدرس التطبيقي، وهما حزمة تطوير البرامج (SDK) من Flutter ومحرِّر.
يمكنك تشغيل ورشة التعلم البرمجي باستخدام أيّ من الأجهزة التالية:
- جهاز Android (ننصح باستخدامه لتنفيذ ميزة "الترجيع التوقّعي" في الخطوة 7) أو iOS متصل بالكمبيوتر ومُعدّ للاستخدام في وضع المطوّر
- محاكي iOS (يتطلب تثبيت أدوات Xcode)
- محاكي Android (يتطلب الإعداد في "استوديو Android")
- متصفّح (يجب استخدام Chrome لتصحيح الأخطاء)
- جهاز كمبيوتر مكتبي يعمل بنظام التشغيل Windows أو Linux أو macOS يجب إجراء عملية التطوير على النظام الأساسي الذي تنوي نشر التطبيق عليه. لذلك، إذا أردت تطوير تطبيق متوافق مع أجهزة الكمبيوتر المكتبي التي تعمل بنظام التشغيل Windows، عليك إجراء عملية التطوير على نظام التشغيل Windows للوصول إلى سلسلة الإنشاء المناسبة. هناك متطلبات خاصة بنظام التشغيل يتم تناولها بالتفصيل على docs.flutter.dev/desktop.
التحقّق من عملية التثبيت
للتأكّد من ضبط إعدادات حزمة تطوير البرامج (SDK) من Flutter بشكلٍ صحيح، ومن تثبيت واحد على الأقل من الأنظمة الأساسية المستهدفة أعلاه، استخدِم أداة Flutter Doctor:
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.24.2, on macOS 14.6.1 23G93 darwin-arm64, locale en) [✓] Android toolchain - develop for Android devices [✓] Xcode - develop for iOS and macOS [✓] Chrome - develop for the web [✓] Android Studio [✓] IntelliJ IDEA Ultimate Edition [✓] VS Code [✓] Connected device (4 available) [✓] Network resources • No issues found!
3- تشغيل التطبيق النموذجي
تنزيل تطبيق "البدء"
استخدِم git
لاستنساخ تطبيق البدء من مستودع flutter/samples على GitHub.
$ git clone https://github.com/flutter/codelabs.git $ cd codelabs/animations/step_01/
بدلاً من ذلك، يمكنك تنزيل الرمز المصدر كملف zip.
تشغيل التطبيق
لتشغيل التطبيق، استخدِم الأمر flutter run
وحدِّد جهازًا مستهدفًا، مثل android
أو ios
أو chrome
. للحصول على القائمة الكاملة للأنظمة الأساسية المتوافقة، يُرجى الاطّلاع على صفحة الأنظمة الأساسية المتوافقة.
$ flutter run -d android
يمكنك أيضًا تشغيل التطبيق وتصحيح أخطاءه باستخدام بيئة تطوير البرامج المفضّلة لديك. يمكنك الاطّلاع على مستندات Flutter الرسمية للحصول على مزيد من المعلومات.
جولة في الرمز
التطبيق الأوّلي هو لعبة اختبار من خيارات متعدّدة تتألّف من شاشتَين وفقًا لنمط تصميم نموذج العارض والعارض والنموذج (MVVM). يستخدم QuestionScreen
(عرض) فئة QuizViewModel
(نموذج العرض) لطرح أسئلة خيارات متعدّدة على المستخدم من فئة QuestionBank
(النموذج).
- home_screen.dart: لعرض شاشة تتضمّن زر لعبة جديدة
- main.dart: لضبط
MaterialApp
لاستخدام Material 3 وعرض الشاشة الرئيسية - model.dart: لتحديد الفئات الأساسية المستخدَمة في جميع أنحاء التطبيق
- question_screen.dart: لعرض واجهة المستخدم للعبة الاختبار
- view_model.dart: لتخزين حالة لعبة "اختبارات" ومنطقها، والتي يتم عرضها من خلال
QuestionScreen
لا يتيح التطبيق أي تأثيرات متحركة حتى الآن، باستثناء انتقال العرض التلقائي الذي تعرضه فئة Navigator
في Flutter عندما يضغط المستخدم على الزر لعبة جديدة.
4. استخدام تأثيرات الصور المتحركة الضمنية
إنّ الرسوم المتحرّكة الضمنية هي خيار رائع في العديد من الحالات، لأنّها لا تتطلّب أيّ إعدادات خاصة. في هذا القسم، ستُعدّل التطبيق المصغّر StatusBar
لكي يعرض لوحة نتائج متحركة. للعثور على تأثيرات الصور المتحركة الضمنية الشائعة، تصفَّح مستندات واجهة برمجة التطبيقات ImplicitlyAnimatedWidget.
إنشاء أداة لوحة النتائج غير المتحرّكة
أنشئ ملفًا جديدًا، lib/scoreboard.dart
باستخدام الرمز البرمجي التالي:
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
Icon(
Icons.star,
size: 50,
color:
score < i + 1 ? Colors.grey.shade400 : Colors.yellow.shade700,
)
],
),
);
}
}
بعد ذلك، أضِف تطبيق Scoreboard
المصغّر في عناصر تطبيق StatusBar
المصغّر، واستبدِل تطبيقات Text
المصغّرة التي كانت تعرض سابقًا النتيجة وإجمالي عدد الأسئلة. من المفترض أن يضيف المحرِّر تلقائيًا import "scoreboard.dart"
المطلوبة في أعلى الملف.
lib/question_screen.dart
class StatusBar extends StatelessWidget {
final QuizViewModel viewModel;
const StatusBar({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Scoreboard( // NEW
score: viewModel.score, // NEW
totalQuestions: viewModel.totalQuestions, // NEW
),
],
),
),
);
}
}
تعرِض هذه الأداة المصغّرة رمز نجمة لكل سؤال. عند الإجابة عن سؤال بشكل صحيح، تضيء نجمة أخرى على الفور بدون أي صورة متحركة. في الخطوات التالية، ستساعد في إبلاغ المستخدم بأنّه تم تغيير نتيجته من خلال إضافة تأثيرات متحركة على حجمها ولونها.
استخدام تأثير صور متحركة ضمني
أنشئ تطبيقًا مصغّرًا جديدًا باسم AnimatedStar
يستخدم تطبيقًا مصغّرًا AnimatedScale
لتغيير مقدار scale
من 0.5
إلى 1.0
عندما يصبح النجم نشطًا:
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
AnimatedStar( // NEW
isActive: score > i, // NEW
) // NEW
],
),
);
}
}
class AnimatedStar extends StatelessWidget { // Add from here...
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: Icon(
Icons.star,
size: 50,
color: isActive ? _activatedColor : _deactivatedColor,
),
);
}
} // To here.
الآن، عندما يجيب المستخدم عن سؤال بشكل صحيح، يعدّل التطبيق المصغّر AnimatedStar
حجمه باستخدام صورة متحركة ضمنية. لا يتم عرض color
في Icon
بشكل متحرك، بل يتم عرض scale
فقط، وذلك من خلال تطبيق AnimatedScale
المصغّر.
استخدام Tween لإجراء عملية الاستقراء بين قيمتَين
يُرجى ملاحظة أنّ لون التطبيق المصغّر AnimatedStar
يتغيّر فورًا بعد تغيير الحقل isActive
إلى true.
لتحقيق تأثير لون متحرك، يمكنك محاولة استخدام تطبيق مصغّر AnimatedContainer
(وهو فئة فرعية أخرى من ImplicitlyAnimatedWidget
)، لأنّه يمكنه إضافة حركة تلقائية إلى جميع سماته، بما في ذلك اللون. يجب أن يعرض التطبيق المصغّر رمزًا، وليس حاوية.
يمكنك أيضًا تجربة AnimatedIcon
الذي يطبّق تأثيرات انتقالية بين أشكال الرموز. ولكن لا يتوفّر تطبيق تلقائي لرمز النجمة في فئة AnimatedIcons
.
بدلاً من ذلك، سنستخدم فئة فرعية أخرى من ImplicitlyAnimatedWidget
تُسمى TweenAnimationBuilder
، والتي تأخذ Tween
كمَعلمة. الفاصل الزمني هو فئة تأخذ قيمتَين (begin
وend
) وتحسب القيم بين القيمتَين، حتى يتمكّن المؤثر المتحرك من عرضها. في هذا المثال، سنستخدم ColorTween
، الذي يستوفي واجهة Tween<Color>
المطلوبة لإنشاء تأثير الرسوم المتحركة.
اختَر التطبيق المصغّر Icon
واستخدِم الإجراء السريع "التفاف باستخدام أداة الإنشاء" في بيئة تطوير البرامج المتكاملة (IDE)، ثم غيِّر الاسم إلى TweenAnimationBuilder
. بعد ذلك، أدخِل المدة وColorTween
.
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: TweenAnimationBuilder( // Add from here...
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) { // To here.
return Icon(
Icons.star,
size: 50,
color: value, // Modify from here...
);
}, // To here.
),
);
}
}
الآن، أعِد تحميل التطبيق بسرعة للاطّلاع على الصورة المتحركة الجديدة.
يُرجى العِلم أنّ قيمة end
في ColorTween
تتغيّر استنادًا إلى قيمة المَعلمة isActive
. ويرجع ذلك إلى أنّ TweenAnimationBuilder
يعيد تشغيل الرسوم المتحركة كلما تغيّرت قيمة Tween.end
. عند حدوث ذلك، يتم تشغيل الصورة المتحركة الجديدة من قيمة الصورة المتحركة الحالية إلى القيمة النهائية الجديدة، ما يتيح لك تغيير اللون في أي وقت (حتى أثناء تشغيل الصورة المتحركة) وعرض تأثير صورة متحركة سلس بالقيم الصحيحة بين القيم.
تطبيق منحنى
يتم تشغيل هذين التأثيرَين للرسوم المتحركة بمعدّل ثابت، ولكن غالبًا ما تكون الرسوم المتحركة أكثر تشويقًا وإفادة من الناحية المرئية عند تسريعها أو إبطاءها.
يطبّق Curve
دالة تمويه، والتي تحدّد معدّل تغيُّر مَعلمة معيّنة بمرور الوقت. يتضمّن Flutter مجموعة من منحنيات التخفيف المُنشأة مسبقًا في فئة Curves
، مثل easeIn
أو easeOut
.
تقدّم هذه المخطّطات البيانية (المتوفّرة في Curves
صفحة مستندات واجهة برمجة التطبيقات) لمحة عن كيفية عمل المنحنيات. تحوّل المنحنيات قيمة الإدخال بين 0.0 و1.0 (المعروضة على محور x) إلى قيمة ناتجة بين 0.0 و1.0 (المعروضة على محور y). تعرض هذه المخططات البيانية أيضًا معاينة لشكل تأثيرات الصور المتحركة المختلفة عند استخدام منحنى التخفيف.
أنشِئ حقلًا جديدًا في AnimatedStar باسم _curve
وأرسِله كمَعلمة إلى التطبيقَين المصغّرَين AnimatedScale
وTweenAnimationBuilder
.
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
final Curve _curve = Curves.elasticOut; // NEW
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
curve: _curve, // NEW
duration: _duration,
child: TweenAnimationBuilder(
curve: _curve, // NEW
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) {
return Icon(
Icons.star,
size: 50,
color: value,
);
},
),
);
}
}
في هذا المثال، يقدّم منحنى elasticOut
تأثيرًا مبالغًا فيه للانعطاف يبدأ بحركة انعطاف ويتوازن باتجاه النهاية.
أعِد تحميل التطبيق فورًا للاطّلاع على هذا المنحنى المطبَّق على AnimatedSize
وTweenAnimationBuilder
.
استخدام "أدوات مطوّري البرامج" لتفعيل الصور المتحركة البطيئة
لتصحيح أخطاء أي تأثير متحرك، توفّر Flutter DevTools طريقة لإبطاء جميع الصور المتحركة في تطبيقك، حتى تتمكّن من رؤية الصورة المتحركة بوضوح أكبر.
لفتح "أدوات المطوّر"، تأكَّد من تشغيل التطبيق في وضع تصحيح الأخطاء، وافتح أداة فحص التطبيقات المصغّرة من خلال اختيارها في شريط أدوات تصحيح الأخطاء في VSCode أو من خلال اختيار الزر فتح أدوات المطوّر في Flutter في نافذة أداة تصحيح الأخطاء في IntelliJ / Android Studio.
بعد فتح أداة فحص التطبيقات المصغّرة، انقر على الزر الحركات البطيئة في شريط الأدوات.
5- استخدام تأثيرات رسوم متحركة فاضحة
مثل الصور المتحركة الضمنية، الصور المتحركة الصريحة هي تأثيرات صور متحركة مُنشأة مسبقًا، ولكن بدلاً من استخدام قيمة مستهدَفة، تستخدِم عنصر Animation
كمَعلمة. وهذا يجعلها مفيدة في الحالات التي يكون فيها المقطع المتحرك محدّدًا مسبقًا من خلال انتقال تنقّل أو AnimatedSwitcher
أو AnimationController
، على سبيل المثال.
استخدام تأثير رسوم متحركة واضح
للبدء بتأثير متحرك واضح، احط التطبيق المصغّر Card
برمز AnimatedSwitcher
.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
), // NEW
);
}
}
يستخدم AnimatedSwitcher
تأثير تمويه مُعدّ مسبقًا، ولكن يمكنك إلغاء ذلك باستخدام المَعلمة transitionBuilder
. يقدّم أداة إنشاء الانتقالات التطبيق المصغّر الثانوي الذي تم تمريره إلى AnimatedSwitcher
وعنصر Animation
. هذه فرصة رائعة لاستخدام صورة متحركة واضحة.
في هذا الدرس التطبيقي حول الترميز، أول حركة معروضة بوضوح سنستخدمها هي SlideTransition
، التي تأخذ Animation<Offset>
الذي يحدّد المدة الزمنية التي ستتحرك خلالها التطبيقات المصغّرة الواردة والصادرة بين بداية ونهاية الفاصل الزمني.
تتضمّن العناصر الانتقالية دالة مساعدة، وهي animate()
، التي تحوّل أي Animation
إلى Animation
آخر مع تطبيق العنصر الانتقالي. وهذا يعني أنّه يمكن استخدام Tween<Offset>
لتحويل Animation<double>
المقدَّمة من AnimatedSwitcher
إلى Animation<Offset>
، لتقديمها إلى التطبيق المصغّر SlideTransition
.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
transitionBuilder: (child, animation) { // Add from here...
final curveAnimation =
CurveTween(curve: Curves.easeInCubic).animate(animation);
final offsetAnimation =
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
.animate(curveAnimation);
return SlideTransition(position: offsetAnimation, child: child);
}, // To here.
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
يُرجى العلم أنّ هذا الإجراء يستخدم Tween.animate
لتطبيق Curve
على Animation
، ثم تحويله من Tween<double>
تتراوح قيمته بين 0.0 و1.0 إلى Tween<Offset>
ينتقل من -0.1 إلى 0.0 على محور x.
بدلاً من ذلك، تحتوي فئة Animation على دالة drive()
تأخذ أي Tween
(أو Animatable
) وتحوّله إلى Animation
جديد. يتيح ذلك "تسلسل" العناصر الانتقالية، ما يجعل الرمز الناتج أكثر إيجازًا:
lib/question_screen.dart
transitionBuilder: (child, animation) {
var offsetAnimation = animation
.drive(CurveTween(curve: Curves.easeInCubic))
.drive(Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero));
return SlideTransition(position: offsetAnimation, child: child);
},
من المزايا الأخرى لاستخدام الرسوم المتحركة الواضحة أنّه يمكن دمجها بسهولة. أضِف صورة متحركة صريحة أخرى، وهي FadeTransition التي تستخدم الصورة المتحركة المنحنية نفسها من خلال لفّ أداة SlideTransition.
lib/question_screen.dart
return AnimatedSwitcher(
transitionBuilder: (child, animation) {
final curveAnimation =
CurveTween(curve: Curves.easeInCubic).animate(animation);
final offsetAnimation =
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
.animate(curveAnimation);
final fadeInAnimation = curveAnimation; // NEW
return FadeTransition( // NEW
opacity: fadeInAnimation, // NEW
child: SlideTransition(position: offsetAnimation, child: child), // NEW
); // NEW
},
تخصيص layoutBuilder
قد تلاحظ مشكلة بسيطة في AnimationSwitcher. عندما تنتقل بطاقة السؤال إلى سؤال جديد، يتم عرضها في وسط المساحة المتوفّرة أثناء تشغيل الصورة المتحركة، ولكن عند إيقاف الصورة المتحركة، يتم تثبيت التطبيق المصغّر في أعلى الشاشة. يؤدي ذلك إلى ظهور حركة متقطّعة لأنّ الموضع النهائي لبطاقة السؤال لا يتطابق مع الموضع أثناء تشغيل الحركة.
لحلّ هذه المشكلة، يحتوي AnimatedSwitcher أيضًا على مَعلمة layoutBuilder، والتي يمكن استخدامها لتحديد التخطيط. استخدِم هذه الدالة لضبط "أداة إنشاء التنسيق" لمحاذاة البطاقة في أعلى الشاشة:
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
layoutBuilder: (currentChild, previousChildren) {
return Stack(
alignment: Alignment.topCenter,
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
);
},
هذا الرمز هو نسخة معدَّلة من defaultLayoutBuilder من فئة AnimatedSwitcher، ولكنّه يستخدم Alignment.topCenter
بدلاً من Alignment.center
.
ملخّص
- الرسوم المتحركة الصريحة هي تأثيرات للحركة تأخذ عنصرًا للحركة (على عكس ImplicitlyAnimatedWidgets التي تأخذ قيمة ومدة مستهدَفَين).
- تمثّل فئة Animation صورة متحركة قيد التشغيل، ولكنها لا تحدّد تأثيرًا معيّنًا.
- استخدِم Tween().animate أو Animation.drive() لتطبيق Tweens وCurves (باستخدام CurveTween) على صورة متحركة.
- استخدِم مَعلمة layoutBuilder في AnimatedSwitcher لتعديل طريقة عرض عناصره الفرعية.
6- التحكّم في حالة صورة متحركة
حتى الآن، كان إطار العمل يشغّل كلّ صورة متحركة تلقائيًا. يتم تشغيل الصور المتحركة الضمنية تلقائيًا، وتتطلّب تأثيرات الصور المتحركة الصريحة استخدام صورة متحركة حتى تعمل بشكل صحيح. في هذا القسم، ستتعرّف على كيفية إنشاء عناصر متحركة باستخدام AnimationController، واستخدام TweenSequence لدمج Tweens معًا.
تشغيل صورة متحركة باستخدام AnimationController
لإنشاء صورة متحركة باستخدام AnimationController، عليك اتّباع الخطوات التالية:
- إنشاء عنصر تحكم StatefulWidget
- استخدِم عنصر SingleTickerProviderStateMixin في فئة State لتقديم أداة Ticker إلى AnimationController.
- يمكنك إعداد AnimationController في طريقة دورة حياة initState، مع توفير عنصر State الحالي للمَعلمة
vsync
(TickerProvider). - تأكَّد من إعادة إنشاء التطبيق المصغّر كلما أرسل AnimationController إشعارًا إلى مستمعيه، إما باستخدام AnimatedBuilder أو من خلال استدعاء listen() وsetState يدويًا.
أنشئ ملفًا جديدًا باسم flip_effect.dart وانسخ التعليمة البرمجية التالية والصقها فيه:
lib/flip_effect.dart
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
@override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: widget.duration);
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
}
@override
void didUpdateWidget(covariant CardFlipEffect oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.child.key != oldWidget.child.key) {
_handleChildChanged(widget.child, oldWidget.child);
}
}
void _handleChildChanged(Widget newChild, Widget previousChild) {
_previousChild = previousChild;
_animationController.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationController.value * math.pi),
child: _animationController.isAnimating
? _animationController.value < 0.5
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
}
تُعدّ هذه الفئة AnimationController وتُعيد تشغيل الصورة المتحركة كلما استدعى إطار العمل didUpdateWidget لإعلامه بأنّ إعدادات التطبيق المصغّر قد تغيّرت، وقد يكون هناك تطبيق مصغّر فرعي جديد.
يضمن AnimatedBuilder إعادة إنشاء شجرة التطبيقات المصغّرة كلما أبلغ AnimationController عن ذلك للمستمعِين، ويتم استخدام التطبيق المصغّر Transform لتطبيق تأثير دوران ثلاثي الأبعاد لمحاكاة قلب البطاقة.
لاستخدام هذا التطبيق المصغّر، عليك تضمين كل بطاقة إجابة في تطبيق مصغّر CardFlipEffect. احرص على تقديم key
إلى التطبيق المصغّر "البطاقة":
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card.filled( // NEW
key: ValueKey(answers[index]), // NEW
color: color,
elevation: 2,
margin: EdgeInsets.all(8),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTapped(index),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(
answers.length > index ? answers[index] : '',
style: Theme.of(context).textTheme.titleMedium,
overflow: TextOverflow.clip,
),
),
),
),
), // NEW
);
}),
);
}
الآن، أعِد تحميل التطبيق بسرعة لمعرفة ما إذا كانت بطاقات الإجابة تنقلب باستخدام التطبيق المصغّر CardFlipEffect.
قد تلاحظ أنّ هذه الفئة تشبه إلى حد كبير تأثيرًا صريحًا للصورة المتحركة. في الواقع، غالبًا ما يكون من المفيد توسيع فئة AnimatedWidget مباشرةً لتنفيذ إصدارك الخاص. بما أنّ هذه الفئة تحتاج إلى تخزين التطبيق المصغّر السابق في حالته، يجب استخدام StatefulWidget. للتعرّف على مزيد من المعلومات عن إنشاء تأثيرات متحركة صريحة، يمكنك الاطّلاع على مستندات واجهة برمجة التطبيقات AnimatedWidget.
إضافة تأخير باستخدام TweenSequence
في هذا القسم، ستضيف تأخيرًا إلى التطبيق المصغّر CardFlipEffect لكي يتم قلب كل بطاقة واحدة تلو الأخرى. للبدء، أضِف حقلًا جديدًا باسم delayAmount
.
lib/flip_effect.dart
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
final double delayAmount; // NEW
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
required this.delayAmount, // NEW
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
بعد ذلك، أضِف delayAmount
إلى طريقة الإنشاء AnswerCards
.
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect(
delayAmount: index.toDouble() / 2, // NEW
duration: const Duration(milliseconds: 300),
child: Card.filled(
key: ValueKey(answers[index]),
بعد ذلك، في _CardFlipEffectState
، أنشئ صورة متحركة جديدة تطبّق التأخير باستخدام TweenSequence
. يُرجى العلم أنّ هذا الإجراء لا يستخدم أي أدوات من مكتبة dart:async
، مثل Future.delayed
. ويعود السبب في ذلك إلى أنّ التأخير هو جزء من الحركة وليس شيئًا يتحكّم فيه التطبيق المصغّر بشكل صريح عند استخدام AnimationController. يسهّل ذلك تصحيح أخطاء تأثير الرسوم المتحركة عند تفعيل الرسوم المتحركة البطيئة في "أدوات مطوّري البرامج"، لأنّه يستخدم TickerProvider نفسه.
لاستخدام TweenSequence
، أنشئ عنصرَي TweenSequenceItem
، أحدهما يحتوي على ConstantTween
يحافظ على قيمة التأثير عند 0 لمدة نسبية وTween
عادي يتراوح من 0.0
إلى 1.0
.
lib/flip_effect.dart
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
late final Animation<double> _animationWithDelay; // NEW
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: widget.duration * (widget.delayAmount + 1));
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
_animationWithDelay = TweenSequence<double>([ // NEW
if (widget.delayAmount > 0) // NEW
TweenSequenceItem( // NEW
tween: ConstantTween<double>(0.0), // NEW
weight: widget.delayAmount, // NEW
), // NEW
TweenSequenceItem( // NEW
tween: Tween(begin: 0.0, end: 1.0), // NEW
weight: 1.0, // NEW
), // NEW
]).animate(_animationController); // NEW
}
أخيرًا، استبدِل الصورة المتحركة لـ AnimationController بالصورة المتحركة الجديدة التي تتضمّن تأخيرًا في طريقة الإنشاء.
lib/flip_effect.dart
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationWithDelay, // Modify this line
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationWithDelay.value * math.pi), // And this line
child: _animationController.isAnimating
? _animationWithDelay.value < 0.5 // And this one.
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
الآن، أعِد تحميل التطبيق بسرعة وراقِب البطاقات وهي تقلب واحدة تلو الأخرى. لتجربة جديدة، جرِّب تغيير المنظور للتأثير الثلاثي الأبعاد الذي يوفّره تطبيق Transform
.
7- استخدام عمليات انتقال مخصّصة للتنقّل
لقد اطّلعنا حتى الآن على كيفية تخصيص التأثيرات على شاشة واحدة، ولكن هناك طريقة أخرى لاستخدام الصور المتحركة وهي استخدامها للانتقال بين الشاشات. في هذا القسم، ستتعرّف على كيفية تطبيق تأثيرات الصور المتحركة على عمليات الانتقال بين الشاشات باستخدام تأثيرات الصور المتحركة المضمّنة وتأثيرات الصور المتحركة المُعدّة مسبقًا والمميّزة التي تقدّمها حزمة animations الرسمية على pub.dev.
إضافة تأثير متحرك إلى انتقال التنقّل
فئة PageRouteBuilder
هي Route
تتيح لك تخصيص الرسوم المتحركة للانتقال. يتيح لك هذا الإجراء إلغاء طلب الاستدعاء transitionBuilder
الذي يقدّم عنصرَي Animation يمثّلان الصورة المتحركة الواردة والصادرة التي يشغّلها المُستكشف.
لتخصيص الرسم المتحرك للانتقال، استبدِل MaterialPageRoute
بـ PageRouteBuilder
، ولخصيص الرسم المتحرك للانتقال عندما ينتقل المستخدم من HomeScreen
إلى QuestionScreen
. استخدِم FadeTransition (أداة مصمّمة بصريًا للحركة) لكي تظهر الشاشة الجديدة بشكل تدريجي فوق الشاشة السابقة.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder( // NEW
pageBuilder: (context, animation, secondaryAnimation) { // NEW
return QuestionScreen(); // NEW
}, // NEW
transitionsBuilder: // NEW
(context, animation, secondaryAnimation, child) { // NEW
return FadeTransition( // NEW
opacity: animation, // NEW
child: child, // NEW
); // NEW
}, // NEW
), // NEW
);
},
child: Text('New Game'),
),
توفّر حزمة الرسوم المتحركة تأثيرات رسوم متحركة رائعة مُعدّة مسبقًا، مثل FadeThroughTransition. استورِد حزمة الصور المتحركة واستبدِل FadeTransition بالتطبيق المصغّر FadeThroughTransition:
lib/home_screen.dart
import 'package;animations/animations.dart';
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition( // NEW
animation: animation, // NEW
secondaryAnimation: secondaryAnimation, // NEW
child: child, // NEW
); // NEW
},
),
);
},
child: Text('New Game'),
),
تخصيص الصورة المتحركة لإيماءة الرجوع إلى الخلف التنبؤية
"الترجيع التوقّعي" هي ميزة جديدة في Android تتيح للمستخدم الاطّلاع على المسار أو التطبيق الحاليَين لمعرفة ما يليهما قبل الانتقال إليهما. يتم تشغيل الرسوم المتحركة للاطّلاع من خلال موقع إصبع المستخدم أثناء سحبه للخلف على الشاشة.
يتيح Flutter ميزة "الرجوع التوقّعي" للنظام من خلال تفعيل الميزة على مستوى النظام عندما لا يتوفّر لفلاتر مسارات لعرضها في حزمة التنقّل، أو بعبارة أخرى، عندما يؤدي الرجوع إلى الخروج من التطبيق. يعالج النظام هذا التأثير المتحرك وليس Flutter نفسه.
تتيح Flutter أيضًا ميزة "الرجوع التوقّعي" عند الانتقال بين المسارات داخل تطبيق Flutter. يرصد عنصر PageTransitionsBuilder الخاص المُسمى PredictiveBackPageTransitionsBuilder
إيماءات الرجوع التوقّعي في النظام ويشغّل عملية انتقال الصفحة وفقًا لتقدّم الإيماءة.
لا تتوفّر ميزة "الرجوع التوقّعي" إلا في الإصدار U من Android والإصدارات الأحدث، ولكن سيستخدم Flutter بشكلٍ سلس سلوك إيماءة الرجوع الأصلي وZoomPageTransitionBuilder. يمكنك الاطّلاع على مشاركة المدونة لمعرفة المزيد من المعلومات، بما في ذلك قسم حول كيفية إعدادها في تطبيقك.
في إعدادات ThemeData لتطبيقك، اضبط PageTransitionsTheme لاستخدام PredictiveBack على Android، وتأثير الانتقال من خلال التمويه من حزمة الرسوم المتحرّكة على المنصات الأخرى:
lib/main.dart
import 'package:animations/animations.dart'; // NEW
import 'package:flutter/material.dart';
import 'home_screen.dart';
void main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), // NEW
TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.macOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.windows: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.linux: FadeThroughPageTransitionsBuilder(), // NEW
},
),
),
home: HomeScreen(),
);
}
}
يمكنك الآن تغيير طلب Navigator.push() إلى MaterialPageRoute.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
MaterialPageRoute(builder: (context) { // NEW
return const QuestionScreen(); // NEW
}), // NEW
);
},
child: Text('New Game'),
),
استخدام FadeThroughTransition لتغيير السؤال الحالي
لا يوفّر تطبيق AnimatedSwitcher سوى صورة متحركة واحدة في ردّ اتصال المُنشئ. لحلّ هذه المشكلة، توفّر حزمة animations
عنصر PageTransitionSwitcher.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher( // NEW
layoutBuilder: (entries) { // NEW
return Stack( // NEW
alignment: Alignment.topCenter, // NEW
children: entries, // NEW
); // NEW
}, // NEW
transitionBuilder: (child, animation, secondaryAnimation) { // NEW
return FadeThroughTransition( // NEW
animation: animation, // NEW
secondaryAnimation: secondaryAnimation, // NEW
child: child, // NEW
); // NEW
}, // NEW
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
استخدام OpenContainer
يقدّم التطبيق المصغّر OpenContainer من حزمة animations
تأثيرًا متحركًا لتحويل الحاوية يتوسّع لإنشاء رابط مرئي بين تطبيقَين مصغّرَين.
يتم عرض التطبيق المصغّر الذي يعرضه closedBuilder
في البداية، ويتم توسيعه ليشمل التطبيق المصغّر الذي يعرضه openBuilder
عند النقر على الحاوية أو عند استدعاء دالة الاستدعاء openContainer
.
لربط دالة الاستدعاء openContainer
بنموذج العرض، أضِف تمريرة جديدة لنموذج العرض في التطبيق المصغّر QuestionCard واحفظ دالة استدعاء ستُستخدَم لعرض شاشة "انتهت اللعبة":
lib/question_screen.dart
class QuestionScreen extends StatefulWidget {
const QuestionScreen({super.key});
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
late final QuizViewModel viewModel =
QuizViewModel(onGameOver: _handleGameOver);
VoidCallback? _showGameOverScreen; // NEW
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: viewModel,
builder: (context, child) {
return Scaffold(
appBar: AppBar(
actions: [
TextButton(
onPressed:
viewModel.hasNextQuestion && viewModel.didAnswerQuestion
? () {
viewModel.getNextQuestion();
}
: null,
child: const Text('Next'),
)
],
),
body: Center(
child: Column(
children: [
QuestionCard( // NEW
onChangeOpenContainer: _handleChangeOpenContainer, // NEW
question: viewModel.currentQuestion?.question, // NEW
viewModel: viewModel, // NEW
), // NEW
Spacer(),
AnswerCards(
onTapped: (index) {
viewModel.checkAnswer(index);
},
answers: viewModel.currentQuestion?.possibleAnswers ?? [],
correctAnswer: viewModel.didAnswerQuestion
? viewModel.currentQuestion?.correctAnswer
: null,
),
StatusBar(viewModel: viewModel),
],
),
),
);
},
);
}
void _handleChangeOpenContainer(VoidCallback openContainer) { // NEW
_showGameOverScreen = openContainer; // NEW
} // NEW
void _handleGameOver() { // NEW
if (_showGameOverScreen != null) { // NEW
_showGameOverScreen!(); // NEW
} // NEW
} // NEW
}
أضِف تطبيقًا مصغّرًا جديدًا، وهو GameOverScreen:
lib/question_screen.dart
class GameOverScreen extends StatelessWidget {
final QuizViewModel viewModel;
const GameOverScreen({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Scoreboard(
score: viewModel.score,
totalQuestions: viewModel.totalQuestions,
),
Text(
'You Win!',
style: Theme.of(context).textTheme.displayLarge,
),
Text(
'Score: ${viewModel.score} / ${viewModel.totalQuestions}',
style: Theme.of(context).textTheme.displaySmall,
),
ElevatedButton(
child: Text('OK'),
onPressed: () {
Navigator.popUntil(context, (route) => route.isFirst);
},
),
],
),
),
);
}
}
في التطبيق المصغّر QuestionCard، استبدِل Card بأداة OpenContainer من حزمة الرسوم المتحرّكة، مع إضافة حقلَين جديدَين لنموذج العرض وطريقة استدعاء الحاوية المفتوحة:
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.onChangeOpenContainer,
required this.question,
required this.viewModel,
super.key,
});
final ValueChanged<VoidCallback> onChangeOpenContainer;
final QuizViewModel viewModel;
static const _backgroundColor = Color(0xfff2f3fa);
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: OpenContainer( // NEW
key: ValueKey(question), // NEW
tappable: false, // NEW
closedColor: _backgroundColor, // NEW
closedShape: const RoundedRectangleBorder( // NEW
borderRadius: BorderRadius.all(Radius.circular(12.0)), // NEW
), // NEW
closedElevation: 4, // NEW
closedBuilder: (context, openContainer) { // NEW
onChangeOpenContainer(openContainer); // NEW
return ColoredBox( // NEW
color: _backgroundColor, // NEW
child: Padding( // NEW
padding: const EdgeInsets.all(16.0), // NEW
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
);
},
openBuilder: (context, closeContainer) { // NEW
return GameOverScreen(viewModel: viewModel); // NEW
}, // NEW
),
);
}
}
8. تهانينا
مبروك، لقد أضفت بنجاح تأثيرات الصور المتحركة إلى تطبيق Flutter، وتعرّفت على المكوّنات الأساسية لنظام الصور المتحركة في Flutter. وتحديدًا، تعرّفت على ما يلي:
- كيفية استخدام ImplicitlyAnimatedWidget
- كيفية استخدام ExplicitlyAnimatedWidget
- كيفية تطبيق منحنيات وتأثيرات انتقالية على صورة متحركة
- كيفية استخدام التطبيقات المصغّرة المُعدّة مسبقًا للانتقالات، مثل AnimatedSwitcher أو PageRouteBuilder
- كيفية استخدام تأثيرات مميّزة مُعدّة مسبقًا من حزمة
animations
، مثل FadeThroughTransition وOpenContainer - كيفية تخصيص الصورة المتحركة التلقائية للانتقال، بما في ذلك إضافة ميزة "الرجوع إلى الخلف بشكلٍ تنبؤي" على Android
الخطوة التالية
اطّلِع على بعض هذه الدروس التطبيقية حول الترميز:
- إنشاء تنسيق تطبيق متجاوب متحرك باستخدام Material 3
- إنشاء انتقالات جميلة باستخدام Material Motion لتطبيق Flutter
- تحويل تطبيقك المطوَّر باستخدام Flutter من تطبيق ممل إلى تطبيق جميل
أو يمكنك تنزيل تطبيق نماذج الصور المتحركة الذي يعرض أساليب مختلفة للصور المتحركة.
مزيد من المعلومات
يمكنك العثور على المزيد من مراجع الرسوم المتحركة على flutter.dev:
- مقدّمة عن الصور المتحركة
- دليل تعليمي حول الصور المتحركة (دليل تعليمي)
- الصور المتحركة الضمنية (دليل تعليمي)
- إضافة تأثيرات متحركة إلى خصائص حاوية (كتاب وصفات)
- إظهار تطبيق مصغّر وإخفائه تدريجيًا (كتاب طبخ)
- الصور المتحركة للعنصر الرئيسي
- إضافة تأثيرات متحركة إلى عملية انتقال مسار الصفحة (كتاب طبخ)
- إضافة تأثيرات متحركة إلى تطبيق مصغّر باستخدام محاكاة فيزيائية (كتاب وصفات)
- الصور المتحركة المُقسّمة
- تطبيقات مصغّرة للصور المتحركة والصور الديناميكية (قائمة التطبيقات المصغّرة)
يمكنك أيضًا الاطّلاع على المقالات التالية على Medium:
- نظرة تفصيلية على الصور المتحركة
- الصور المتحركة التلقائية المخصّصة في Flutter
- إدارة الصور المتحركة باستخدام Flutter وFlux / Redux
- كيفية اختيار تطبيق Flutter Animation Widget المناسب لك؟
- الصور المتحركة الاتجاهية التي تتضمّن صورًا متحركة فاضحة مدمجة
- أساسيات الصور المتحركة في Flutter باستخدام الصور المتحركة الضمنية
- متى يجب استخدام AnimatedBuilder أو AnimatedWidget؟