الرسوم المتحركة الضمنية: AnimatedContainer و AnimatedPadding
الرسوم المتحركة الضمنية: AnimatedContainer و AnimatedPadding
يوفر Flutter فئة قوية من الودجات تُعرف بـ ودجات الرسوم المتحركة الضمنية. تُحرِّك هذه الودجات تغييرات خصائصها تلقائياً في كل مرة تُحدَّث فيها تلك الخصائص داخل setState(). لا تحتاج إلى إدارة AnimationController أو Tween أو Ticker على الإطلاق — فـ Flutter يتولى الاستيفاء (interpolation) خلف الكواليس.
أكثر ودجتي الرسوم المتحركة الضمنية استخداماً هما AnimatedContainer و AnimatedPadding. تقبل كلتاهما معامل duration الذي يتحكم في مدة الانتقال، ومعامل curve الاختياري الذي يُشكِّل تسارع الحركة.
AnimationController.كيف تعمل الرسوم المتحركة الضمنية
تتبع كل ودجت رسوم متحركة ضمنية النمط ذاته:
- تُعلن الودجت ببعض قيم الخصائص الأولية.
- عندما يُسبِّب تغيير الحالة تلقي خاصية واحدة أو أكثر لقيم جديدة، تكتشف الودجت الفرق.
- خلال
durationالمحددة، تستوفي الودجت بسلاسة بين القيمة القديمة والجديدة. - تحترم الرسوم المتحركة
Curveالمختارة التي تتحكم في ملف سرعة الانتقال (مثل التسارع التدريجي، الارتداد، المرونة).
AnimatedContainer
AnimatedContainer هي النسخة المتحركة من Container. يمكنها تحريك أي خاصية تقريباً تدعمها Container: العرض والارتفاع واللون وانحناء الحافة والمحاذاة والحشو والهامش والزينة. هذا يجعلها واحدة من أكثر ودجات الرسوم المتحركة الضمنية تنوعاً في Flutter.
AnimatedContainer — تحريك الحجم واللون
class AnimatedBox extends StatefulWidget {
const AnimatedBox({super.key});
@override
State<AnimatedBox> createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_expanded = !_expanded;
});
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
width: _expanded ? 280.0 : 120.0,
height: _expanded ? 280.0 : 120.0,
decoration: BoxDecoration(
color: _expanded ? Colors.deepPurple : Colors.teal,
borderRadius: BorderRadius.circular(_expanded ? 40.0 : 8.0),
),
alignment: Alignment.center,
child: Text(
_expanded ? 'اضغط للتصغير' : 'اضغط للتوسيع',
style: const TextStyle(color: Colors.white, fontSize: 14),
textAlign: TextAlign.center,
),
),
);
}
}
عندما يضغط المستخدم على الصندوق، يقلب setState قيمة _expanded. تستوفي AnimatedContainer تلقائياً قيم width و height و color و borderRadius خلال 400 ميلي ثانية باستخدام منحنى easeInOut. لا حاجة لأي كود نمطي للرسوم المتحركة.
decoration و color في آنٍ واحد، لكن لا تضبط كلاً من color و BoxDecoration التي تحتوي لوناً في الوقت ذاته. سيُطلق Flutter خطأ تأكيد (assertion error). ضع اللون داخل BoxDecoration عندما تحتاج أيضاً إلى انحناء الحافة أو الظلال.تحريك خصائص متعددة في آنٍ واحد
إحدى نقاط قوة AnimatedContainer هي أن كل خاصية تُغيِّرها في استدعاء setState واحد تُحرَّك في آنٍ واحد. يمكنك تحريك العرض والارتفاع واللون وانحناء الحافة والحشو والمحاذاة كلها في انتقال واحد.
AnimatedContainer — تأثير تمييز البطاقة
class HighlightCard extends StatefulWidget {
final String label;
const HighlightCard({super.key, required this.label});
@override
State<HighlightCard> createState() => _HighlightCardState();
}
class _HighlightCardState extends State<HighlightCard> {
bool _selected = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _selected = !_selected),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.fastOutSlowIn,
margin: EdgeInsets.all(_selected ? 4.0 : 12.0),
padding: EdgeInsets.symmetric(
vertical: _selected ? 24.0 : 16.0,
horizontal: _selected ? 32.0 : 16.0,
),
decoration: BoxDecoration(
color: _selected ? Colors.indigo : Colors.white,
borderRadius: BorderRadius.circular(_selected ? 20.0 : 4.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(_selected ? 0.25 : 0.08),
blurRadius: _selected ? 16.0 : 4.0,
offset: const Offset(0, 4),
),
],
),
child: Text(
widget.label,
style: TextStyle(
color: _selected ? Colors.white : Colors.black87,
fontWeight:
_selected ? FontWeight.bold : FontWeight.normal,
),
),
),
);
}
}
AnimatedPadding
AnimatedPadding هي ودجت رسوم متحركة ضمنية متخصصة تُحرِّك فقط الحشو حول ودجتها الفرعية. إنها أخف وزناً من AnimatedContainer عندما تحتاج حصراً إلى نقل قيم الحشو — مثلاً، تمرير المحتوى للداخل أو الخارج استجابةً لتفاعل المستخدم أو ظهور لوحة المفاتيح.
AnimatedPadding — إزاحة المحتوى عند ظهور لوحة المفاتيح
class FocusableForm extends StatefulWidget {
const FocusableForm({super.key});
@override
State<FocusableForm> createState() => _FocusableFormState();
}
class _FocusableFormState extends State<FocusableForm> {
bool _focused = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedPadding(
duration: const Duration(milliseconds: 350),
curve: Curves.easeOut,
padding: EdgeInsets.only(top: _focused ? 8.0 : 60.0),
child: const FlutterLogo(size: 80),
),
Focus(
onFocusChange: (hasFocus) {
setState(() => _focused = hasFocus);
},
child: const TextField(
decoration: InputDecoration(
labelText: 'اسم المستخدم',
border: OutlineInputBorder(),
),
),
),
],
);
}
}
عندما يكتسب حقل النص التركيز، ينزلق الشعار بسلاسة للأعلى بالانتقال من top: 60.0 إلى top: 8.0 — محاكياً نوع إزاحة المحتوى الشائعة في شاشات تسجيل الدخول على الجوّال.
اختيار المنحنى المناسب
يتحكم معامل curve في كيفية تقدم الرسوم المتحركة بمرور الوقت. يشحن Flutter العديد من المنحنيات المدمجة في فئة Curves:
- Curves.linear — سرعة ثابتة، إحساس ميكانيكي.
- Curves.easeIn — بداية بطيئة ونهاية سريعة.
- Curves.easeOut — بداية سريعة وتباطؤ طبيعي.
- Curves.easeInOut — بداية ونهاية بطيئتان ووسط سريع. الأكثر طبيعية لانتقالات واجهة المستخدم.
- Curves.fastOutSlowIn — منحنى الحركة القياسي لـ Material Design.
- Curves.bounceOut — يتجاوز الهدف ويرتد عند النهاية.
- Curves.elasticOut — تأثير ارتداد نابضي مرن.
Curves.easeInOut أو Curves.fastOutSlowIn الأكثر صقلاً ويتبع إرشادات حركة Material Design. احتفظ بمنحنيات الارتداد والمرونة للتفاعلات المرحة الشبيهة بالألعاب.الخلاصة
ودجات الرسوم المتحركة الضمنية هي أسهل نقطة دخول في Flutter إلى تصميم الحركة. تُتيح لك AnimatedContainer تحريك الحجم واللون وانحناء الحافة والحشو والهامش والزينة في ودجة واحدة بمجرد تحديث الحالة. توفر AnimatedPadding بديلاً خفيفاً عندما يحتاج المسافات فقط إلى التحريك. تقبل كلتاهما duration و curve، مما يمنحك تحكماً كاملاً في طابع الانتقال دون أي كود تحكم بالرسوم المتحركة. كقاعدة عامة، الجأ إلى الودجات الضمنية أولاً، وانتقل إلى الرسوم المتحركة الصريحة فقط عندما تحتاج إلى تحكم دقيق في التسلسل أو التكرار.
Animated ويوجد نظيرها غير المتحرك (مثل Container → AnimatedContainer، و Opacity → AnimatedOpacity)، فإنها تتبع نمط الرسوم المتحركة الضمنية: غيِّر قيمة الخاصية داخل setState() وسيُحرِّك Flutter الانتقال تلقائياً.