منحنيات الرسوم المتحركة: التحكم في طبيعة الحركة
منحنيات الرسوم المتحركة: التحكم في طبيعة الحركة
في Flutter، يُحدِّد منحنى الرسوم المتحركة كيفية تقدم القيمة المتحركة من نقطة البداية إلى نقطة النهاية عبر الزمن. بدون منحنى، تتقدم كل رسوم متحركة بوتيرة ثابتة — وهو سلوك يُعرف بـالحركة الخطية. غير أن الأجسام في العالم الحقيقي نادراً ما تتحرك بهذه الطريقة. باب يفتح ببطء ثم يتسارع ثم يتباطأ بلطف حتى يتوقف. تتيح المنحنيات تكرار هذا الطابع الفيزيائي في واجهة المستخدم، مما يمنح الانتقالات طابعاً طبيعياً ومتقناً.
تُمثَّل المنحنيات بالفئة المجردة Curve الموجودة في dart:ui والمكشوفة من خلال فئة الثوابت Curves في Flutter. كل منحنى يربط قيمة إدخال في النطاق [0.0, 1.0] بقيمة إخراج، تقع أيضاً عادةً ضمن [0.0, 1.0] (وإن كانت منحنيات التجاوز قد تتعدى هذه الحدود مؤقتاً).
تطبيق منحنى باستخدام CurvedAnimation
الطريقة الأكثر شيوعاً لتطبيق منحنى هي تغليف AnimationController داخل CurvedAnimation. يعمل CurvedAnimation كمزيِّن: يمرر نفس تدفق الإطارات لكنه يحوِّل كل قيمة من خلال المنحنى المختار قبل أن يقرأها أي Tween أو AnimatedWidget.
الاستخدام الأساسي لـ CurvedAnimation
class FadeInBox extends StatefulWidget {
const FadeInBox({super.key});
@override
State<FadeInBox> createState() => _FadeInBoxState();
}
class _FadeInBoxState extends State<FadeInBox>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacity;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
// تغليف المتحكم داخل CurvedAnimation
final curved = CurvedAnimation(
parent: _controller,
curve: Curves.easeOut, // بداية بطيئة، وسط سريع، نهاية بطيئة
);
_opacity = Tween<double>(begin: 0.0, end: 1.0).animate(curved);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _opacity,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
);
}
}
مكتبة المنحنيات المدمجة
يأتي Flutter مزوداً بعشرات المنحنيات الجاهزة في فئة Curves، وتنتمي إلى عدة عائلات:
منحنيات Ease
Curves.linear— سرعة ثابتة؛ لا تسارع. يُستخدم بحذر إذ يبدو آلياً في الغالب.Curves.ease— تسارع لطيف ثم تباطؤ (الافتراضي في CSS). خيار آمن للأغراض العامة.Curves.easeIn— بداية بطيئة، نهاية سريعة. مناسب للعناصر المغادِرة للشاشة.Curves.easeOut— بداية سريعة، نهاية بطيئة. مناسب للعناصر القادمة إلى الشاشة.Curves.easeInOut— بداية بطيئة، وسط سريع، نهاية بطيئة. سلس ومتوازن.Curves.easeInOutCubic— نسخة أقوى وأكثر سينمائية منeaseInOut.
منحنيات مستوحاة من الفيزياء
Curves.bounceIn/Curves.bounceOut/Curves.bounceInOut— يحاكي كرة مرتدة.bounceOutهو الأكثر طبيعية (الكائن يستقر عند الوجهة).Curves.elasticIn/Curves.elasticOut/Curves.elasticInOut— تجاوز وتذبذب شبيه بالنابض. يُستخدم باعتدال؛ الأنسب لواجهات المستخدم المرحة وأسلوب الألعاب.
التباطؤ والتجاوز
Curves.decelerate— دخول سريع، تباطؤ تدريجي؛ يُستخدم كثيراً لانزلاق الألواح السفلية.Curves.fastOutSlowIn— المنحنى القياسي في Material Design. تسارع قوي ثم تباطؤ طويل وسلس.Curves.slowMiddle— سريع في الطرفين، هادئ في المنتصف.
Curves.fastOutSlowIn لمعظم رسوم الدخول وCurves.fastLinearToSlowEaseIn لرسوم الخروج. اتباع هذه الافتراضيات يضمن تجانس تطبيقك مع النظام الأساسي.مقارنة المنحنيات جنباً إلى جنب
أفضل طريقة لاستيعاب شكل كل منحنى هي تحريك نفس الخاصية بمنحنيات مختلفة في آنٍ واحد. المثال التالي يُزحزح أربعة حاويات نحو اليمين على مدار ثانية واحدة، كلٌّ منها يستخدم منحنىً مختلفاً:
مقارنة المنحنيات جنباً إلى جنب
class CurveComparisonPage extends StatefulWidget {
const CurveComparisonPage({super.key});
@override
State<CurveComparisonPage> createState() => _CurveComparisonPageState();
}
class _CurveComparisonPageState extends State<CurveComparisonPage>
with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
// متحكم واحد يقود المنحنيات الأربعة
late Animation<double> _linear;
late Animation<double> _easeOut;
late Animation<double> _bounceOut;
late Animation<double> _elasticOut;
@override
void initState() {
super.initState();
_ctrl = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_linear = Tween<double>(begin: 0, end: 200)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.linear));
_easeOut = Tween<double>(begin: 0, end: 200)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOut));
_bounceOut = Tween<double>(begin: 0, end: 200)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.bounceOut));
_elasticOut = Tween<double>(begin: 0, end: 200)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.elasticOut));
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('مقارنة المنحنيات')),
body: AnimatedBuilder(
animation: _ctrl,
builder: (context, _) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_track('linear', _linear.value, Colors.grey),
_track('easeOut', _easeOut.value, Colors.blue),
_track('bounceOut', _bounceOut.value, Colors.green),
_track('elasticOut', _elasticOut.value, Colors.red),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _ctrl.forward(from: 0),
child: const Icon(Icons.play_arrow),
),
);
}
Widget _track(String label, double offset, Color color) {
return Row(
children: [
SizedBox(width: 90, child: Text(label)),
Transform.translate(
offset: Offset(offset, 0),
child: Container(
width: 30, height: 30,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
),
],
);
}
}
منحنيات مخصصة باستخدام Cubic Bezier
عندما لا يتطابق أيٌّ من المنحنيات المدمجة مع نيتك التصميمية، يمكنك إنشاء منحنى مخصص باستخدام Cubic. هذا يعكس دالة cubic-bezier() في CSS ويقبل أربعة معاملات لنقاط التحكم: a وb وc وd.
منحنى Cubic مخصص
// منحنى easeOut حاد جداً: بداية سريعة، تباطؤ مفاجئ
const snappyEaseOut = Cubic(0.22, 1.0, 0.36, 1.0);
final animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(parent: controller, curve: snappyEaseOut),
);
elasticOut تُنتج قيماً خارج [0.0, 1.0] لفترة وجيزة. إذا طبَّقت مثل هذا المنحنى على خاصية ذات حدود صارمة (مثل Opacity التي تُقيِّد القيم إلى [0.0, 1.0])، فسيُقصَّر التجاوز بصمت ويضيع تأثير النابض. استخدم منحنيات التجاوز على خصائص غير مقيَّدة كإزاحات الإزاحة أو قيم المقياس عوضاً عن ذلك.منحنيات العكس
يقبل CurvedAnimation معاملاً اختيارياً reverseCurve، يُطبَّق عند تشغيل المتحكم بشكل عكسي. هذا قيِّم لإنشاء انتقالات دخول/خروج غير متماثلة تبدو صحيحة فيزيائياً — مثلاً، قد ينزلق درج إلى الداخل بـeaseOut لكنه ينزلق للخارج بـeaseIn.
منحنى دخول/خروج غير متماثل
final curved = CurvedAnimation(
parent: _controller,
curve: Curves.easeOut, // يُستخدم عند التشغيل للأمام
reverseCurve: Curves.easeIn, // يُستخدم عند التشغيل بالعكس
);
ملخص
منحنيات الرسوم المتحركة أداة بسيطة لكنها قوية لتشكيل شعور الحركة. النقاط الرئيسية:
- غلِّف
AnimationControllerداخلCurvedAnimationلتطبيق المنحنى. - استخدم منحنيات عائلة ease للانتقالات القياسية دخولاً وخروجاً.
- استخدم منحنيات الارتداد والمرونة للتأثيرات المرحة اللافتة للانتباه — لكن باعتدال.
- فضِّل
Curves.fastOutSlowInافتراضياً للامتثال لمعايير Material Design. - استخدم
Cubicلتعريف منحنيات مخصصة دقيقة تتوافق مع مواصفات التصميم. - قدِّم
reverseCurveللسلوك غير المتماثل بين التشغيل الأمامي والعكسي.