~/icsd.ir — bash
SYSTEM_ONLINE

Widget‌ها در Flutter

«همه چیز در Flutter یک Widget است.» این جمله طلایی Flutter است. در این فصل با مفهوم Widget، انواع آن، چرخه حیات و BuildContext آشنا می‌شویم.

۳.۱ مقدمه

«همه چیز در Flutter یک Widget است.» این جمله طلایی Flutter است. در این فصل با مفهوم Widget، انواع آن، چرخه حیات و BuildContext آشنا می‌شویم.

۳.۲ Widget چیست؟

Widget یک توضیح غیرقابل تغییر (immutable description) از بخشی از UI است. Flutter آن را به یک Element و سپس RenderObject تبدیل می‌کند.


لایه‌های Flutter:

Widget (immutable description)
   ↓ creates
Element (mutable, lifecycle)
   ↓ creates
RenderObject (layout, paint)
    

دو نوع Widget اصلی

StatelessWidget

بدون state داخلی. وقتی parameter ها تغییر کنند، widget جدید ساخته می‌شود.

مثل: Text، Icon، Image

StatefulWidget

state داخلی دارد. می‌تواند خودش را با setState() rebuild کند.

مثل: Checkbox، Slider، فرم

۳.۳ StatelessWidget


import "package:flutter/material.dart";

class GreetingCard extends StatelessWidget {
  final String name;
  final String message;
  
  // const constructor — performance بهتر
  const GreetingCard({
    super.key,
    required this.name,
    this.message = "خوش آمدید!",
  });
  
  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              "سلام $name",
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: 8),
            Text(message),
          ],
        ),
      ),
    );
  }
}

// استفاده
class MyHome extends StatelessWidget {
  const MyHome({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("صفحه اصلی")),
      body: Column(
        children: const [
          GreetingCard(name: "علی"),
          GreetingCard(name: "زهرا", message: "روز خوبی داشته باشید"),
        ],
      ),
    );
  }
}
    

چرا const؟

اگر یک widget در زمان compile قابل ساخت است (همه پارامترهایش const)، با const یک نسخه canonical آن ساخته می‌شود. این یعنی Flutter آن را rebuild نمی‌کند — performance بهتر!

۳.۴ StatefulWidget


class Counter extends StatefulWidget {
  final int initialValue;
  
  const Counter({super.key, this.initialValue = 0});
  
  @override
  State createState() => _CounterState();
}

class _CounterState extends State {
  late int _count;
  
  @override
  void initState() {
    super.initState();
    _count = widget.initialValue;  // دسترسی به widget با this.widget
    print("Counter initialized");
  }
  
  void _increment() {
    setState(() {
      _count++;
    });
  }
  
  void _decrement() {
    setState(() {
      _count--;
    });
  }
  
  @override
  void dispose() {
    print("Counter disposed");
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: const Icon(Icons.remove),
          onPressed: _decrement,
        ),
        Text(
          "$_count",
          style: Theme.of(context).textTheme.headlineLarge,
        ),
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: _increment,
        ),
      ],
    );
  }
}
    

چرا State جداست از Widget؟

چون Widget باید immutable باشد. State در یک object جدا نگه‌داری می‌شود که بین rebuilds حفظ می‌شود. این طراحی Flutter را بسیار کارآمد می‌کند.

۳.۵ Lifecycle یک StatefulWidget


1. createState()              ← Flutter صدا می‌زند
       ↓
2. initState()                 ← فقط یک بار، شروع
       ↓
3. didChangeDependencies()     ← اگر InheritedWidget تغییر کند
       ↓
4. build()                     ← بارها اجرا می‌شود
       ↓ (rebuild)
5. didUpdateWidget()           ← اگر parent rebuild کند
       ↓
6. setState() → build() again
       ↓
7. deactivate()                ← قبل از حذف از tree
       ↓
8. dispose()                   ← یک بار، پایان
    

class LifecycleDemo extends StatefulWidget {
  final String title;
  const LifecycleDemo({super.key, required this.title});
  
  @override
  State createState() => _LifecycleDemoState();
}

class _LifecycleDemoState extends State {
  late TextEditingController _controller;
  Timer? _timer;
  
  @override
  void initState() {
    super.initState();
    print("1. initState");
    _controller = TextEditingController();
    _timer = Timer.periodic(const Duration(seconds: 1), (_) {
      print("tick");
    });
  }
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("2. didChangeDependencies");
    // مثلاً MediaQuery، Theme در دسترس است
  }
  
  @override
  void didUpdateWidget(covariant LifecycleDemo oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("3. didUpdateWidget: ${oldWidget.title} -> ${widget.title}");
    // اگر title عوض شده، کاری انجام دهیم
  }
  
  @override
  Widget build(BuildContext context) {
    print("4. build");
    return TextField(controller: _controller);
  }
  
  @override
  void deactivate() {
    print("5. deactivate");
    super.deactivate();
  }
  
  @override
  void dispose() {
    print("6. dispose");
    // ⚠️ حتماً resources را آزاد کنید
    _controller.dispose();
    _timer?.cancel();
    super.dispose();
  }
}
    
هشدار: فراموش کردن dispose() برای Controller، AnimationController، StreamSubscription و Timer منجر به memory leak می‌شود!

۳.۶ BuildContext

BuildContext در واقع reference به Element فعلی در tree است. از طریق آن:

  • به Theme دسترسی دارید: Theme.of(context)
  • به MediaQuery دسترسی: MediaQuery.of(context).size
  • Navigation: Navigator.of(context).push(...)
  • Inherited Widgets: Provider.of<X>(context)
  • Localization: AppLocalizations.of(context)

class ResponsiveText extends StatelessWidget {
  const ResponsiveText({super.key});
  
  @override
  Widget build(BuildContext context) {
    // اطلاعات از context
    final mediaQuery = MediaQuery.of(context);
    final theme = Theme.of(context);
    final isDark = theme.brightness == Brightness.dark;
    
    final fontSize = mediaQuery.size.width < 600 ? 16.0 : 24.0;
    
    return Text(
      "متن واکنش‌گرا",
      style: TextStyle(
        fontSize: fontSize,
        color: isDark ? Colors.white : Colors.black,
      ),
    );
  }
}
    

۳.۷ Widget‌های ضروری

Text — نمایش متن


Text("سلام")

Text(
  "متن استایل‌دار",
  style: TextStyle(
    fontSize: 18,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
    fontFamily: "Vazirmatn",
    letterSpacing: 0.5,
    height: 1.5,
  ),
  textAlign: TextAlign.center,
  maxLines: 2,
  overflow: TextOverflow.ellipsis,
)

// متن غنی با چند استایل
RichText(
  text: const TextSpan(
    style: TextStyle(color: Colors.black),
    children: [
      TextSpan(text: "Flutter "),
      TextSpan(
        text: "خیلی",
        style: TextStyle(fontWeight: FontWeight.bold, color: Colors.red),
      ),
      TextSpan(text: " قدرتمند است"),
    ],
  ),
)
    

Container — جعبه چندمنظوره


Container(
  width: 200,
  height: 100,
  margin: const EdgeInsets.all(16),
  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(12),
    border: Border.all(color: Colors.black, width: 2),
    gradient: const LinearGradient(
      colors: [Colors.blue, Colors.purple],
    ),
    boxShadow: [
      BoxShadow(
        color: Colors.black26,
        blurRadius: 8,
        offset: const Offset(0, 4),
      ),
    ],
  ),
  child: const Center(child: Text("Container", style: TextStyle(color: Colors.white))),
)
    

Image — نمایش تصویر


// از Asset
Image.asset("assets/images/logo.png")

// از URL
Image.network(
  "https://example.com/photo.jpg",
  loadingBuilder: (context, child, progress) {
    if (progress == null) return child;
    return Center(
      child: CircularProgressIndicator(
        value: progress.expectedTotalBytes != null
            ? progress.cumulativeBytesLoaded / progress.expectedTotalBytes!
            : null,
      ),
    );
  },
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.error);
  },
  fit: BoxFit.cover,
)

// با cached_network_image (پیشنهاد)
CachedNetworkImage(
  imageUrl: "https://example.com/photo.jpg",
  placeholder: (_, __) => const CircularProgressIndicator(),
  errorWidget: (_, __, ___) => const Icon(Icons.error),
)

// از File
Image.file(File("/path/to/image.jpg"))
    

Icon — آیکون‌های Material و Cupertino


const Icon(Icons.home, size: 32, color: Colors.blue)
const Icon(Icons.favorite, color: Colors.red)
const Icon(Icons.shopping_cart)

// Cupertino (iOS-style)
const Icon(CupertinoIcons.heart_fill)
    

Button‌ها


// 1. ElevatedButton — بارز
ElevatedButton(
  onPressed: () => print("کلیک"),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue,
    foregroundColor: Colors.white,
    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(8),
    ),
  ),
  child: const Text("ثبت‌نام"),
)

// 2. TextButton — مسطح
TextButton(
  onPressed: () {},
  child: const Text("لغو"),
)

// 3. OutlinedButton — با حاشیه
OutlinedButton(
  onPressed: () {},
  child: const Text("بیشتر"),
)

// 4. IconButton
IconButton(
  icon: const Icon(Icons.search),
  onPressed: () {},
  tooltip: "جستجو",
)

// 5. FloatingActionButton
FloatingActionButton(
  onPressed: () {},
  child: const Icon(Icons.add),
)

// 6. با icon و label
ElevatedButton.icon(
  icon: const Icon(Icons.send),
  label: const Text("ارسال"),
  onPressed: () {},
)

// disabled کردن: onPressed: null
ElevatedButton(
  onPressed: null,  // disabled
  child: const Text("غیرفعال"),
)
    

Scaffold — ساختار صفحه


Scaffold(
  appBar: AppBar(
    title: const Text("عنوان"),
    actions: [
      IconButton(icon: const Icon(Icons.search), onPressed: () {}),
      IconButton(icon: const Icon(Icons.menu), onPressed: () {}),
    ],
  ),
  body: const Center(child: Text("بدنه اصلی")),
  drawer: Drawer(
    child: ListView(
      children: const [
        DrawerHeader(child: Text("منو")),
        ListTile(title: Text("خانه")),
        ListTile(title: Text("تنظیمات")),
      ],
    ),
  ),
  bottomNavigationBar: BottomNavigationBar(
    items: const [
      BottomNavigationBarItem(icon: Icon(Icons.home), label: "خانه"),
      BottomNavigationBarItem(icon: Icon(Icons.search), label: "جستجو"),
      BottomNavigationBarItem(icon: Icon(Icons.person), label: "پروفایل"),
    ],
    currentIndex: 0,
    onTap: (index) {},
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  ),
)
    

۳.۸ Keys — کِی استفاده کنیم؟

Key به Flutter می‌گوید کدام widget با کدام Element مطابقت دارد. در اکثر موارد لازم نیست، اما در موارد خاص حیاتی است:


// مشکل بدون Key:
class TodoList extends StatefulWidget {
  // وقتی item های لیست reorder می‌شوند، state اشتباه می‌شود
}

// راه حل: Key
ListView(
  children: items.map((item) => TodoTile(
    key: ValueKey(item.id),  // ← مهم
    item: item,
  )).toList(),
)
    

انواع Key

  • ValueKey: از یک value (مثل id)
  • UniqueKey: همیشه unique (هر rebuild جدید)
  • ObjectKey: از reference یک object
  • GlobalKey: در همه widget tree unique

کِی Key استفاده کنیم؟

  • وقتی لیستی از StatefulWidget های مشابه دارید
  • وقتی reorder/insert/delete در لیست انجام می‌دهید
  • برای دسترسی به State از خارج (با GlobalKey)

۳.۹ بهترین تجربیات Widget

  1. const هر کجا ممکن. performance بهتر، rebuild کمتر.
  2. Widget کوچک نگه دارید. هر method جدا = widget جدا.
  3. extract widget با Refactor. به جای method، widget بسازید.
  4. StatelessWidget اگر state ندارد. سبک‌تر است.
  5. dispose را فراموش نکنید. Controller، Timer، Stream.
  6. Key وقتی نیاز است. در لیست‌های قابل reorder.
  7. BuildContext را به async نسپارید. اگر widget unmount شود، crash می‌شود. با mounted چک کنید.
  8. Theme و MediaQuery از context. hardcode نکنید.
  9. final برای فیلدهای widget. چون immutable است.
  10. required برای پارامترهای ضروری.

۳.۱۰ خلاصه فصل

آنچه آموختیم:
  • «همه چیز Widget است» — flutter philosophy
  • StatelessWidget: بدون state، از parameter ها
  • StatefulWidget: state داخلی با setState
  • Lifecycle: initState، didUpdateWidget، dispose
  • BuildContext: دسترسی به Theme، MediaQuery، Navigation
  • Widget‌های ضروری: Text، Container، Image، Icon، Buttons، Scaffold
  • Keys برای identify widget ها در لیست
در فصل بعد: Layout — Row، Column، Stack، ListView، GridView و طراحی UI واکنش‌گرا.

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.