Widget هو المفهوم المركزي لإطار Flutter، حيث يصف تكوين عنصر واجهة المستخدم. كل مكون بصري، من زر إلى رسم متحرك معقد، هو Widget. على عكس الأطر الأخرى حيث يتم وصف واجهة المستخدم بملفات XML منفصلة أو يتم رسمها أمراً، يبني Flutter الواجهة من خلال تكوين Widgets — دمج العناصر الصغيرة غير القابلة للتجزئة في شجرة هرمية. وفقاً لوثائق Flutter (2025)، تضم مكتبة Flutter SDK أكثر من 260 Widget مدمج تغطي Material Design و Cupertino والأنماط المخصصة.
الرئيسية
Widget في Flutter هو وصف غير قابل للتغيير (immutable) لجزء من واجهة المستخدم. كل Widget يحتوي على خصائص تكوين: الحجم، اللون، الموضع، النص، معالجات الأحداث و Widgets فرعية. Widgets نفسها لا تُرسم مباشرة — إنها مخططات (blueprints) ينشئ بناءً عليها محرك Flutter كائن RenderObject، وهو الكائن الرسومي الفعلي على الشاشة.
تقول فلسفة Flutter: «كل شيء هو Widget». هذا يعني أنه ليس فقط العناصر المرئية (Text, Image, Button) هي Widgets، بل أيضاً الكتل الهيكلية (Padding, Center, Column, Stack)، والكتل السلوكية (GestureDetector, AnimatedBuilder) وحتى التطبيق نفسه (MaterialApp, CupertinoApp). يضمن هذا النهج التوحيد: يمكن دمج أي عنصر شاشة مع أي عنصر آخر من خلال التداخل البسيط.
وفقاً لمؤتمر Google I/O 2024 — Flutter Widgets Deep Dive، يحتوي التطبيق المتوسط على Flutter على 200 إلى 1500 Widget في أي لحظة. على الرغم من هذه الكمية، يحافظ Flutter على 60 إطاراً في الثانية حتى على الأجهزة منخفضة الميزانية بفضل التحسينات على مستوى محرك C++ Skia/Impeller. Widgets هي كائنات خفيفة (40–80 بايت لكل منها)، لذا فإن إنشائها ليس عنق زجاجة للأداء.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('مرحباً، Flutter!'),
),
),
);
}
}
لفهم كيفية عمل Widget، عليك فهم بنية Flutter التي تتكون من ثلاث أشجار مترابطة. الأولى — شجرة Widget — تصف تكوين واجهة المستخدم. إنها شجرة خفيفة يمكن إعادة بنائها بالكامل في كل إطار (يزيل جامع القمامة Widgets القديمة وينشئ جديدة). Widgets غير قابلة للتغيير: إذا تغير لون النص، يتم إنشاء Widget Text جديد باللون الجديد، ويتم التخلص من القديم.
الشجرة الثانية — شجرة Element — هي الرابط بين Widget و RenderObject. Element يحتوي على مرجع إلى Widget (التكوين) وإلى RenderObject (الرسم). عندما يتغير Widget، يقارن Flutter Widget الجديد مع Element القديم ويقرر: تحديث RenderObject الموجود (إذا كان Widget من نفس النوع) أو إنشاء واحد جديد (إذا تغير نوع Widget). تسمى هذه العملية Reconciliation وهي مشابهة لـ Virtual DOM في React.
الشجرة الثالثة — شجرة RenderObject — مسؤولة عن الرسم الفعلي على الشاشة. RenderObject يحتوي على أحجام ومواقع وطرق رسم محددة. محرك Flutter (C++ Skia أو Impeller) يجتاز شجرة RenderObject ويرسم كل عقدة. شجرة RenderObject هي الأثقل، لذا يقلل Flutter من تغييراتها بإعادة استخدام RenderObjects عند التبديل إلى Widgets من نفس النوع.
| الشجرة | الغرض | غير قابل للتغيير؟ | دورة الحياة |
|---|---|---|---|
| Widget | تكوين واجهة المستخدم (مخطط) | نعم | يعاد إنشاؤه في كل build |
| Element | رابط Widget ↔ RenderObject | لا | موجود طالما الأداة في الشجرة |
| RenderObject | الرسم والتخطيط | لا | ثقيل، يعاد استخدامه عند الإمكان |
يقسم Flutter Widgets إلى نوعين أساسيين: StatelessWidget و StatefulWidget. StatelessWidget هو أداة لا تحتوي على حالة قابلة للتغيير. مظهر StatelessWidget محدد بالكامل بواسطة المنشئ ولا يمكن أن يتغير بعد الرسم. أمثلة: Text, Icon, Divider, Padding. جميع خصائص StatelessWidget تُعلن كـ final في المنشئ وهي للقراءة فقط.
StatefulWidget هو أداة ذات حالة قابلة للتغيير. يتكون من فئتين: Widget نفسه (تكوين غير قابل للتغيير، مثل StatelessWidget) و State (حالة قابلة للتغيير). فصل Widget عن State هو قرار معماري رئيسي في Flutter. يتم إعادة إنشاء Widget في كل build، لكن كائن State يستمر في العيش طوال دورة حياة الأداة في الشجرة، محافظاً على حالته.
عند استدعاء setState()، يضع Flutter علامة على State كـ «متسخ» وفي الإطار التالي يستدعي طريقة build() لإعادة بناء الشجرة الفرعية. المهم: setState() لا يعيد إنشاء Widget نفسه — بل يستدعي build() في State الموجود. هذا يعني أن StatefulWidget يمكنه تحديث واجهة المستخدم دون فقدان حالة Widgets الفرعية، طالما بقيت مفاتيح (Key) العناصر الفرعية مستقرة.
// StatelessWidget — المظهر لا يتغير أبداً
class GreetingWidget extends StatelessWidget {
const GreetingWidget({super.key, required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Text('مرحباً، $name');
}
}
// StatefulWidget — عداد بحالة قابلة للتغيير
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('العدد: $_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('زيادة'),
),
],
);
}
}
جميع Widgets في Flutter يمكن تقسيمها إلى ثلاث فئات رئيسية حسب الغرض الوظيفي. Widgets التخطيط — مسؤولة عن ترتيب العناصر الفرعية على الشاشة. Row و Column يرتبان الأطفال في خط، Stack يضع واحداً فوق الآخر، Expanded و Flexible يوزعان المساحة المتاحة. Widgets التخطيط ليس لها تمثيل بصري خاص بها — فهي تدير موضع وحجم الأدوات الفرعية.
Widgets الرسم — مسؤولة عن التنسيق البصري. Container يدمج الزخارف (اللون، التدرج، الظل، الحدود) مع خصائص التخطيط. Padding يضيف تباعداً، DecoratedBox يرسم خلفية، Transform يطبق تحويلات (دوران، قياس). Widgets الرسم هي لبنات بناء النمط البصري وتستخدم غالباً مع Widgets التخطيط لتحقيق المظهر المطلوب.
Widgets التفاعل — تعالج إدخال المستخدم. GestureDetector يكتشف الإيماءات (النقر، السحب، القرص)، InkWell يضيف تأثير التموج لـ Material، TextField يقبل إدخال النص، Slider و Switch يوفران عناصر تحكم قياسية. Widgets التفاعل ترفع الأحداث عبر دوال الاسترجاع التي تمرر إلى المنشئ أو تُعالج عبر موفري الحالة.
| الفئة | أمثلة Widget | الغرض |
|---|---|---|
| تخطيط | Row, Column, Stack, Expanded, Flexible, Align | تحديد موضع وحجم العناصر الفرعية |
| رسم | Container, Padding, DecoratedBox, RotatedBox | اللون، الخلفية، الحدود، الظلال، التحويلات |
| تفاعل | GestureDetector, InkWell, TextField, Slider | معالجة اللمس، الإدخال، الإيماءات |
| منصة | MaterialApp, CupertinoApp, Theme, MediaQuery | التكامل مع المنصة، السمات، التكيف |
| غير متزامن | FutureBuilder, StreamBuilder, ValueListenableBuilder | تحديث تفاعلي من بيانات غير متزامنة |
وفقاً لـ Flutter Widget of the Week (Google, 2025)، يستخدم مجتمع Flutter بنشاط مزيجاً من Widgets التخطيط + الرسم + التفاعل لبناء أي واجهة تقريباً. على سبيل المثال، زر: InkWell (تفاعل) + Container (رسم) + Text (ثابت) + Padding (تخطيط). تسمح هذه النمطية بإعادة استخدام الكتل القياسية في سياقات مختلفة دون تكرار الكود.
تكوين Widget هو عملية بناء واجهة المستخدم عبر تداخل بعض Widgets داخل أخرى. على عكس الوراثة الكلاسيكية (extends) حيث ترث الفئة الفرعية سلوك الأصل، يستخدم Flutter التجميع: كل Widget يحتوي على Widgets أخرى عبر معلمة child (لواحد) أو children (لعدة). هذا النهج يوفر مرونة أكبر وإعادة استخدام.
BuildContext هو ثاني أهم مفهوم بعد Widget. BuildContext هو واصف لموضع Widget في شجرة العناصر. من خلال BuildContext، يمكن لـ Widget الوصول إلى الأدوات السلف (Theme.of(context), MediaQuery.of(context), Navigator.of(context)). يتم تمرير BuildContext إلى طريقة build() ويستخدم للتفاعل مع العناصر الأم والفرعية. كل Widget لديه BuildContext واحد بالضبط، والذي يحدد بشكل فريد موضعه في الشجرة.
وفقاً للنظرة العامة المعمارية لـ Flutter (Google, 2025)، BuildContext هو أساس InheritedWidget — آلية تسمح بتمرير البيانات إلى أسفل الشجرة دون تمرير صريح عبر المنشئات. Theme, MediaQuery, Navigator و Provider يستخدمون InheritedWidget داخلياً. أي Widget متداخل بعمق يمكنه الوصول إلى بيانات السلف عبر BuildContext.dependOnInheritedWidgetOfExactType، مما يجعل BuildContext مفتاح العمارة التفاعلية لـ Flutter.
// تكوين Widget عبر التداخل
Scaffold(
appBar: AppBar(title: const Text('تطبيقي')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('مرحباً بك في Flutter',
style: TextStyle(fontSize: 24)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () { /* nav */ },
child: const Text('ابدأ'),
),
],
),
),
)
// الوصول إلى السمات عبر BuildContext
Text(
'نص منسق',
style: Theme.of(context).textTheme.headlineMedium,
)
الخطأ الأول والأكثر شيوعاً هو استخدام StatefulWidget حيث يكفي StatelessWidget. العديد من مطوري Flutter المبتدئين ينشئون StatefulWidget لجميع الأدوات، حتى عندما يتم تخزين الحالة في موفر خارجي (Provider, Riverpod, BLoC). هذا مفرط ويضر بالأداء. القاعدة: استخدم StatelessWidget إذا كانت الحالة تدار من الخارج أو إذا لم يكن للأداة حالتها القابلة للتغيير.
الخطأ الثاني — إنشاء Widget داخل طريقة build بدون منشئ const. كل Widget يتم إنشاؤه بدون const يُخصص من جديد في كل build. إذا قمت بإنشاء Widgets بمنشئ const داخل build()، يمكن لـ Flutter إعادة استخدام نفس المثيل، مما يقلل الحمل على جامع القمامة. أضف const أينما أمكن — خاصة لـ Text, Icon, SizedBox, Padding و Widgets عديمة الحالة الأخرى.
الخطأ الثالث — العمل غير الصحيح مع المفاتيح (Key). يستخدم Flutter Key لتحديد Widgets عند إعادة بناء الشجرة. إذا أعيد بناء قائمة Widgets بدون Key، قد يخلط Flutter ترتيب العناصر، مما يؤدي إلى رسوم متحركة غير صحيحة أو فقدان الحالة. أضف دائماً Key (مثل ValueKey أو ObjectKey) للعناصر في القوائم، خاصة عند استخدام ListView.builder مع بيانات ديناميكية.
الأسئلة الشائعة
StatelessWidget — أداة بدون حالة قابلة للتغيير، مظهرها محدد بالكامل بواسطة المنشئ. StatefulWidget — أداة ذات حالة قابلة للتغيير، تُخزن في كائن State منفصل ويمكن تحديثها عبر setState() دون إعادة إنشاء الأداة نفسها. استخدم StatelessWidget حيثما أمكن، StatefulWidget — عندما تكون هناك حاجة إلى حالة محلية.
عدم قابلية التغيير لـ Widgets هو قرار معماري لـ Flutter للأداء. إذا كانت Widgets قابلة للتغيير، لم يكن بإمكان Flutter مقارنة التكوينات القديمة والجديدة بأمان في كل build. تسمح عدم القابلية للتغيير لـ Flutter بتحديد ما إذا كان Widget قد تغير بسرعة (عبر عامل ==) وإعادة استخدام RenderObject الموجود، مما يقلل من عمليات الرسم المكلفة.
BuildContext هو واصف لموضع Widget في شجرة العناصر. من خلاله، يحصل Widget على وصول إلى الأدوات السلف (Theme, MediaQuery, Navigator) و InheritedWidget. يستخدم BuildContext أيضاً للملاحة (Navigator.of(context))، وإظهار SnackBar والتفاعل مع Provider. كل Widget يتلقى BuildContext عبر طريقة build() ويمرره إلى الأحفاد.
استخدم Row للترتيب الأفقي للعناصر، Column للترتيب الرأسي، Stack لتراكب العناصر فوق بعضها البعض. يعمل Row و Column وفق مبدأ flexbox: تشغل الأطفال مساحة وفقاً لـ mainAxisSize و mainAxisAlignment و crossAxisAlignment. يستخدم Stack أطفالاً موضوعة لتحديد المواقع بدقة بالنسبة للحواف أو المركز.
يحقق Flutter أداءً عالياً من خلال ثلاث آليات: (1) Widgets رخيصة — كائنات غير قابلة للتغيير خفيفة (40–80 بايت)، إنشائها لا يثقل GC. (2) إعادة استخدام RenderObject — عند التبديل إلى Widget من نفس النوع، يعاد استخدام RenderObject، متجنباً إعادة الإنشاء المكلفة. (3) محرك Skia/Impeller — الرسم على مستوى C++ مع استدعاءات رسم مصغرة عبر حدود إعادة الرسم.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا