Widget — چیست، انواع و ترکیب در Flutter

نویسنده: IT Sectr منتشر شده: 2026-06-30 زمان مطالعه: 10 دقیقه

Widget — مفهوم مرکزی فریمورک Flutter است که پیکربندی عنصر رابط کاربری را توصیف می‌کند. هر مؤلفه بصری، از دکمه گرفته تا انیمیشن پیچیده، Widget است. برخلاف سایر فریمورک‌ها که UI با فایل‌های XML جداگانه توصیف می‌شود یا به صورت امری نقاشی می‌شود، Flutter رابط را از طریق ترکیب Widget — ترکیب عناصر کوچک غیرقابل تقسیم در یک درخت سلسله‌مراتبی — می‌سازد. طبق Flutter Documentation (2025)، کتابخانه Flutter SDK شامل بیش از 260 Widget داخلی است که Material Design، Cupertino و سبک‌های سفارشی را پوشش می‌دهد.

نکات اصلی

  • Widget — بلوک ساختمانی پایه UI در Flutter، توصیف‌کننده پیکربندی عنصر.
  • ترکیب — UI از طریق تودرتو کردن Widget در یکدیگر ساخته می‌شود، نه از طریق وراثت.
  • StatelessWidget — ویجتی که پس از رندر تغییر نمی‌کند (متن، آیکون، فاصله).
  • StatefulWidget — ویجت با وضعیت قابل تغییر (فرم‌ها، انیمیشن‌ها، لیست‌های داده).
  • Element tree — Flutter سه درخت را نگه می‌دارد: Widget، Element و RenderObject.

Widget در Flutter چیست

Widget در Flutter یک توصیف تغییرناپذیر (immutable) از بخشی از رابط کاربری است. هر Widget شامل ویژگی‌های پیکربندی است: اندازه، رنگ، موقعیت، متن، کنترل‌کننده‌های رویداد و Widgetهای فرزند. خود Widgetها مستقیماً رندر نمی‌شوند — آنها دستورالعمل‌هایی (blueprints) هستند که بر اساس آنها Flutter Engine یک RenderObject — شیء گرافیکی واقعی روی صفحه — ایجاد می‌کند.

فلسفه Flutter می‌گوید: «Everything is a Widget». این بدان معناست که نه تنها عناصر قابل مشاهده (Text، Image، Button) Widget هستند، بلکه بلوک‌های ساختاری (Padding، Center، Column، Stack)، بلوک‌های رفتاری (GestureDetector، AnimatedBuilder) و حتی خود برنامه (MaterialApp، CupertinoApp) نیز Widget هستند. این رویکرد یکپارچگی را تضمین می‌کند: هر عنصر صفحه را می‌توان از طریق تودرتو کردن ساده با هر عنصر دیگر ترکیب کرد.

طبق داده‌های Google I/O 2024 — Flutter Widgets Deep Dive، یک برنامه میانگین در Flutter بین 200 تا 1500 Widget در یک لحظه دارد. با وجود این تعداد، Flutter 60 FPS را حتی در دستگاه‌های ارزان‌قیمت به لطف بهینه‌سازی‌ها در سطح موتور C++ Skia/Impeller حفظ می‌کند. Widget اشیاء سبکی هستند (هر کدام 40–80 بایت)، بنابراین ایجاد آنها تنگنای عملکرد نیست.

dart
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!'),
        ),
      ),
    );
  }
}

سه درخت Flutter: Widget، Element و RenderObject

برای درک نحوه کار Widget، باید معماری Flutter متشکل از سه درخت به هم پیوسته را بشناسید. اولین — Widget tree — پیکربندی UI را توصیف می‌کند. این یک درخت سبک است که می‌تواند در هر فریم به طور کامل بازسازی شود (garbage collector Widgetهای قدیمی را حذف و موارد جدید ایجاد می‌کند). Widgetها تغییرناپذیرند (immutable): اگر رنگ متن تغییر کرد — یک Widget Text جدید با رنگ جدید ایجاد می‌شود، قدیمی کنار گذاشته می‌شود.

دومین درخت — Element tree — حلقه اتصال بین Widget و RenderObject است. Element حاوی ارجاع به Widget (پیکربندی) و RenderObject (رندر) است. هنگامی که Widget تغییر می‌کند، Flutter Widget جدید را با Element قدیمی مقایسه می‌کند و تصمیم می‌گیرد: RenderObject موجود را به‌روز کند (اگر Widget از همان نوع باشد) یا یک مورد جدید ایجاد کند (اگر نوع Widget تغییر کرده باشد). این فرآیند Reconciliation نامیده می‌شود و مشابه React Virtual DOM است.

سومین درخت — RenderObject tree — مسئول رندر واقعی روی صفحه است. RenderObject شامل اندازه‌های مشخص، موقعیت‌ها و متدهای paint است. Flutter Engine (C++ Skia یا Impeller) از RenderObject tree عبور می‌کند و هر گره را رندر می‌کند. RenderObject tree سنگین‌ترین درخت است، بنابراین Flutter تغییرات آن را با استفاده مجدد از RenderObject هنگام تغییر Widget از همان نوع به حداقل می‌رساند.

درختهدفتغییرناپذیر؟چرخه عمر
Widgetپیکربندی UI (دستورالعمل)بلهدر هر build بازآفرینی می‌شود
Elementارتباط Widget ↔ RenderObjectخیرتا زمانی که ویجت در درخت است وجود دارد
RenderObjectرندر و layoutخیرسنگین، در صورت امکان مجدداً استفاده می‌شود

StatelessWidget در مقابل StatefulWidget

Flutter Widgetها را به دو نوع اساسی تقسیم می‌کند: 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 می‌تواند UI را بدون از دست دادن وضعیت Widgetهای فرزند به‌روز کند، تا زمانی که کلیدهای (Key) عناصر فرزند پایدار باقی بمانند.

dart
// 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('افزایش'),
        ),
      ],
    );
  }
}

انواع Widget: layout، painting، interactive

همه Widgetها در Flutter را می‌توان بر اساس هدف عملکردی به سه دسته اصلی تقسیم کرد. Layout Widget — مسئول قرارگیری عناصر فرزند روی صفحه هستند. Row و Column فرزندان را در یک خط قرار می‌دهند، Stack یکی را روی دیگری قرار می‌دهد، Expanded و Flexible فضای موجود را توزیع می‌کنند. Layout Widgetها نمایش بصری خاص خود را ندارند — آنها موقعیت و اندازه ویجت‌های فرزند را مدیریت می‌کنند.

Painting Widget — مسئول آرایش بصری هستند. Container تزئینات (رنگ، گرادیان، سایه، حاشیه) را با ویژگی‌های layout ترکیب می‌کند. Padding فاصله اضافه می‌کند، DecoratedBox پس‌زمینه می‌کشد، Transform تبدیل‌ها (چرخش، مقیاس) را اعمال می‌کند. Painting Widget بلوک‌های سازنده سبک بصری هستند و اغلب برای دستیابی به ظاهر مطلوب با layout Widget استفاده می‌شوند.

Interactive Widget — ورودی کاربر را پردازش می‌کنند. GestureDetector حرکات (tap، swipe، pinch) را تشخیص می‌دهد، InkWell اثر ripple Material را اضافه می‌کند، TextField ورودی متن را دریافت می‌کند، Slider و Switch عناصر کنترل استاندارد را ارائه می‌دهند. Interactive Widget رویدادها را از طریق توابع callback که به سازنده منتقل می‌شوند یا توسط ارائه‌دهندگان وضعیت مدیریت می‌شوند، بالا می‌برند.

دستهنمونه‌های Widgetهدف
LayoutRow, Column, Stack, Expanded, Flexible, Alignموقعیت و اندازه عناصر فرزند
PaintingContainer, Padding, DecoratedBox, RotatedBoxرنگ، پس‌زمینه، حاشیه‌ها، سایه‌ها، تبدیل‌ها
InteractiveGestureDetector, InkWell, TextField, Sliderپردازش لمس، ورودی، حرکات
PlatformMaterialApp, CupertinoApp, Theme, MediaQueryادغام با پلتفرم، تم‌ها، سازگاری
AsyncFutureBuilder, StreamBuilder, ValueListenableBuilderبه‌روزرسانی واکنشی از داده‌های ناهمزمان

طبق داده‌های Flutter Widget of the Week (Google, 2025)، جامعه Flutter به طور فعال از ترکیب layout + painting + interactive Widget برای ساخت تقریباً هر رابطی استفاده می‌کند. به عنوان مثال، دکمه: InkWell (interactive) + Container (painting) + Text (static) + Padding (layout). چنین ماژولاریتی امکان استفاده مجدد از بلوک‌های استاندارد در زمینه‌های مختلف بدون تکرار کد را فراهم می‌کند.

ترکیب Widget و BuildContext

ترکیب Widget فرآیند ساخت UI از طریق تودرتو کردن یک Widget در دیگری است. برخلاف وراثت کلاسیک (extends) که در آن کلاس فرزند رفتار والد را به ارث می‌برد، Flutter از تجمیع استفاده می‌کند: هر Widget حاوی Widgetهای دیگر از طریق پارامتر child (برای یک) یا children (برای چند) است. این رویکرد انعطاف‌پذیری و قابلیت استفاده مجدد بیشتری می‌دهد.

BuildContext دومین مفهوم مهم پس از Widget است. BuildContext — توصیف‌گر موقعیت Widget در درخت عناصر است. از طریق BuildContext، Widget می‌تواند به Widgetهای اجدادی (Theme.of(context)، MediaQuery.of(context)، Navigator.of(context)) دسترسی پیدا کند. BuildContext به متد build() منتقل می‌شود و برای تعامل با عناصر والد و فرزند استفاده می‌شود. هر Widget دقیقاً یک BuildContext دارد که موقعیت آن را در درخت به طور یکتا شناسایی می‌کند.

طبق Flutter Architectural Overview (Google, 2025)، BuildContext پایه InheritedWidget است — مکانیزمی که امکان انتقال داده‌ها به پایین درخت را بدون ارسال صریح از طریق سازنده‌ها فراهم می‌کند. Theme، MediaQuery، Navigator و Provider از InheritedWidget در زیرساخت خود استفاده می‌کنند. هر Widget عمیقاً تودرتو می‌تواند از طریق BuildContext.dependOnInheritedWidgetOfExactType به داده‌های جد خود دسترسی پیدا کند، که BuildContext را به کلید معماری واکنشی Flutter تبدیل می‌کند.

dart
// ترکیب 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,
)

خطاهای رایج با Widget

اولین و رایج‌ترین خطا — استفاده از StatefulWidget در جایی که StatelessWidget کافی است. بسیاری از توسعه‌دهندگان تازه‌کار Flutter برای همه ویجت‌ها StatefulWidget ایجاد می‌کنند، حتی اگر وضعیت در یک ارائه‌دهنده خارجی (Provider، Riverpod، BLoC) ذخیره شده باشد. این اضافی است و عملکرد را کاهش می‌دهد. قانون: اگر وضعیت از خارج مدیریت می‌شود یا ویجت وضعیت قابل تغییر خود را ندارد، از StatelessWidget استفاده کنید.

دومین خطا — ایجاد Widget درون متد build بدون const-سازنده. هر Widget ایجاد شده بدون const در هر build دوباره تخصیص داده می‌شود. اگر درون build() Widgetها با const-سازنده ایجاد شوند، Flutter می‌تواند از همان نمونه مجدداً استفاده کند و بار garbage collector را کاهش دهد. در هر جایی که ممکن است const اضافه کنید — به ویژه برای Text، Icon، SizedBox، Padding و سایر stateless Widgetها.

سومین خطا — کار نادرست با کلیدها (Key). Flutter از Key برای شناسایی Widget هنگام بازسازی درخت استفاده می‌کند. اگر لیستی از Widgetها بدون Key بازسازی شود، Flutter ممکن است ترتیب عناصر را اشتباه بگیرد که منجر به انیمیشن نادرست یا از دست رفتن وضعیت می‌شود. همیشه برای عناصر در لیست‌ها Key (مثلاً ValueKey یا ObjectKey) اضافه کنید، به ویژه هنگام استفاده از ListView.builder با داده‌های پویا.

سوالات متداول

تفاوت بین StatelessWidget و StatefulWidget چیست؟

StatelessWidget — ویجتی بدون وضعیت قابل تغییر، ظاهر آن کاملاً توسط سازنده تعیین می‌شود. StatefulWidget — ویجتی با وضعیت قابل تغییر که در یک شیء جداگانه State ذخیره می‌شود و می‌تواند از طریق setState() بدون بازآفرینی خود ویجت به‌روز شود. در هر جایی که ممکن است از StatelessWidget استفاده کنید، StatefulWidget — زمانی که وضعیت محلی مورد نیاز است.

چرا Widgetها immutable نامیده می‌شوند؟

Widget immutable — این یک تصمیم معماری Flutter برای عملکرد است. اگر Widgetها mutable بودند، Flutter نمی‌توانست با اطمینان پیکربندی قدیمی و جدید را در هر build مقایسه کند. Immutability به Flutter اجازه می‌دهد به سرعت تشخیص دهد که آیا Widget تغییر کرده است (از طریق عملگر ==) و از RenderObject موجود مجدداً استفاده کند و عملیات گران رندر را به حداقل برساند.

BuildContext چیست و چه کاربردی دارد؟

BuildContext — توصیف‌گر موقعیت Widget در درخت عناصر است. از طریق آن Widget به Widgetهای اجدادی (Theme، MediaQuery، Navigator) و InheritedWidget دسترسی پیدا می‌کند. BuildContext همچنین برای ناوبری (Navigator.of(context))، نمایش SnackBar و تعامل با Provider استفاده می‌شود. هر Widget از طریق متد build() یک BuildContext دریافت می‌کند و آن را به جانشینان خود منتقل می‌کند.

چگونه بین Row، Column و Stack برای layout انتخاب کنیم؟

برای چیدمان افقی عناصر از Row، برای عمودی از Column، برای قرار دادن عناصر روی یکدیگر از Stack استفاده کنید. Row و Column بر اساس اصل flexbox کار می‌کنند: children فضا را با توجه به mainAxisSize، mainAxisAlignment و crossAxisAlignment اشغال می‌کنند. Stack از positioned-children برای موقعیت‌یابی دقیق نسبت به لبه‌ها یا مرکز استفاده می‌کند.

چگونه Flutter با هزاران Widget 60 FPS را حفظ می‌کند؟

Flutter عملکرد بالا را از طریق سه مکانیزم به دست می‌آورد: (1) Widget are cheap — اشیاء سبک immutable (40–80 بایت)، ایجاد آنها GC را سنگین نمی‌کند. (2) RenderObject reuse — هنگام تغییر Widget از همان نوع، RenderObject مجدداً استفاده می‌شود و از بازآفرینی گران جلوگیری می‌کند. (3) Skia/Impeller engine — رندر در سطح C++ با به حداقل رساندن فراخوانی‌های نقاشی از طریق repaint boundaries.

خلاصه

  • Widget — پیکربندی immutable بخشی از UI در Flutter، بلوک ساختمانی پایه که ظاهر و رفتار را توصیف می‌کند.
  • سه درخت — Flutter از Widget tree (پیکربندی)، Element tree (ارتباط)، RenderObject tree (رندر) برای رندر بهینه استفاده می‌کند.
  • Stateless در مقابل Stateful — StatelessWidget بدون وضعیت، StatefulWidget با State قابل تغییر و متد setState() برای به‌روزرسانی UI.
  • ترکیب — UI از طریق تودرتو کردن Widget با child/children بدون وراثت ساخته می‌شود که انعطاف‌پذیری و قابلیت استفاده مجدد را فراهم می‌کند.
  • BuildContext — توصیف‌گر موقعیت Widget در درخت برای دسترسی به Theme، Navigator، MediaQuery و InheritedWidget.
  • عملکرد — Widget در هر build بازآفرینی می‌شود (60 FPS)، اما RenderObject در صورت تطابق نوع مجدداً استفاده می‌شود.
  • دسته‌بندی — Widget به layout (Row, Column, Stack)، painting (Container, Padding)، interactive (GestureDetector, TextField) و platform (MaterialApp, Theme) تقسیم می‌شود.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید