لمحة عن هذا الدرس التطبيقي حول الترميز
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
لعرض تأثيرات صور متحركة رائعة باستخدام الحد الأدنى من الإعدادات
المتطلبات
- حزمة تطوير البرامج Flutter SDK
- بيئة تطوير متكاملة، مثل 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
يمكنك أيضًا تشغيل التطبيق وتصحيح أخطاءه باستخدام بيئة تطوير البرامج المتكاملة (IDE) المفضّلة لديك. اطّلِع على مستندات 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(isActive: score > i), // Edit this line.
],
),
);
}
}
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
المطلوبة لإنشاء تأثير الرسوم المتحركة.
اختَر التطبيق المصغّر 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); // And modify this line.
},
),
);
}
}
الآن، أعِد تحميل التطبيق بسرعة للاطّلاع على الصورة المتحركة الجديدة.
يُرجى العِلم أنّ قيمة 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 طريقة لإبطاء جميع الصور المتحركة في تطبيقك، ما يتيح لك رؤية الصورة المتحركة بوضوح أكبر.
لفتح "أدوات المطوّر"، تأكَّد من تشغيل التطبيق في وضع تصحيح الأخطاء، وافتح أداة فحص التطبيقات المصغّرة من خلال اختيارها في شريط أدوات تصحيح الأخطاء في 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
لتحويل Animation
المقدَّمة من AnimatedSwitcher
إلى Animation
، لتقديمها إلى التطبيق المصغّر 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
تتراوح قيمته بين 0.0 و1.0 إلى Tween
ينتقل من -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
. عندما ينتقل QuestionCard
إلى سؤال جديد، يتم عرضه في وسط المساحة المتوفّرة أثناء تشغيل الصورة المتحركة، ولكن عند إيقاف الصورة المتحركة، يتم تثبيت التطبيق المصغّر في أعلى الشاشة. يؤدي ذلك إلى ظهور حركة متقطّعة لأنّ الموضع النهائي لبطاقة السؤال لا يتطابق مع الموضع أثناء تشغيل الحركة.
لحلّ هذه المشكلة، تتضمّن 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
.
ملخّص
- الصور المتحركة الصريحة هي تأثيرات متحركة تأخذ عنصر
Animation
(على عكسImplicitlyAnimatedWidgets
التي تأخذvalue
وduration
هدفَين). - تمثّل فئة
Animation
صورة متحركة قيد التشغيل، ولكنها لا تحدّد تأثيرًا معيّنًا. - استخدِم
Tween().animate
أوAnimation.drive()
لتطبيقTweens
وCurves
(باستخدامCurveTween
) على صورة متحركة. - استخدِم مَعلمة
layoutBuilder
فيAnimatedSwitcher
لتعديل طريقة عرض عناصرها الفرعية.
6. التحكّم في حالة صورة متحركة
حتى الآن، كان إطار العمل يشغّل كلّ صورة متحركة تلقائيًا. يتم تشغيل الصور المتحركة الضمنية تلقائيًا، وتتطلّب تأثيرات الصور المتحركة الصريحة Animation
لكي تعمل بشكل صحيح. في هذا القسم، ستتعرّف على كيفية إنشاء عناصر Animation
باستخدام AnimationController
، واستخدام TweenSequence
لدمج Tween
معًا.
تشغيل صورة متحركة باستخدام 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
لتطبيق Card
المصغّر:
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
مباشرةً لتنفيذ نسختك الخاصة. بما أنّ هذه الفئة تحتاج إلى تخزين التطبيق المصغّر السابق في State
، يجب استخدام 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
، أنشِئ Animation
جديدًا يطبّق التأخير باستخدام 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>([ // Add from here...
if (widget.delayAmount > 0)
TweenSequenceItem(
tween: ConstantTween<double>(0.0),
weight: widget.delayAmount,
),
TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 1.0),
]).animate(_animationController); // To here.
}
أخيرًا، استبدِل الصورة المتحركة AnimationController
بالصورة المتحركة الجديدة ذات التأثير المتأخر في الطريقة build
.
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( // Add from here...
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
), // To here.
);
},
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( // Add from here...
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
); // To here.
},
),
);
},
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),
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( // Add from here...
builder: (context) {
return const QuestionScreen();
},
), // To here.
);
},
child: Text('New Game'),
),
استخدِم FadeThroughTransition لتغيير السؤال الحالي.
لا يوفّر التطبيق المصغّر AnimatedSwitcher
سوى Animation
واحد في ردّ اتصال المُنشئ. لحلّ هذه المشكلة، توفّر حزمة 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( // Add from here...
layoutBuilder: (entries) {
return Stack(alignment: Alignment.topCenter, children: entries);
},
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
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,
),
),
),
);
}
}
استخدام OpenContainer
يقدّم التطبيق المصغّر OpenContainer من حزمة animations
تأثيرًا متحركًا لتحويل الحاوية يتم توسيعه لإنشاء رابط مرئي بين تطبيقَين مصغّرَين.
يتم عرض التطبيق المصغّر الذي يعرضه closedBuilder
في البداية، ويتم توسيعه ليشمل التطبيق المصغّر الذي يعرضه openBuilder
عند النقر على الحاوية أو عند استدعاء دالة الاستدعاء openContainer
.
لربط طلب إعادة الاتصال openContainer
بنموذج العرض، أضِف تمريرة جديدة viewModel
إلى التطبيق المصغّر 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
من حزمة animations
، مع إضافة حقلَين جديدَين لدعوة viewModel
وفتح الحاوية:
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
" - كيفية تطبيق
Curves
وTweens
على صورة متحركة - كيفية استخدام التطبيقات المصغّرة الجاهزة للاستخدام الخاصة بالانتقالات، مثل
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؟