~/icsd.ir — bash
SYSTEM_ONLINE

Layout و طراحی UI

Layout قلب طراحی UI است. در Flutter همه چیز با ترکیب widget های layout ساخته می‌شود — Row، Column، Stack، ListView و... در این فصل تسلط کامل پیدا می‌کنیم.

۴.۱ مقدمه

Layout قلب طراحی UI است. در Flutter همه چیز با ترکیب widget های layout ساخته می‌شود — Row، Column، Stack، ListView و… در این فصل تسلط کامل پیدا می‌کنیم.

۴.۲ Row و Column

پراستفاده‌ترین widget های layout. Row افقی، Column عمودی.


Column(
  // محور اصلی (عمودی برای Column)
  mainAxisAlignment: MainAxisAlignment.center,
  
  // محور متقاطع (افقی برای Column)
  crossAxisAlignment: CrossAxisAlignment.stretch,
  
  // فضای محور اصلی
  mainAxisSize: MainAxisSize.max,  // یا min
  
  children: [
    Container(height: 50, color: Colors.red),
    Container(height: 50, color: Colors.green),
    Container(height: 50, color: Colors.blue),
  ],
)

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: [
    Icon(Icons.home),
    Text("خانه"),
    Icon(Icons.arrow_forward),
  ],
)
    

MainAxisAlignment options


start          | A B C - - - - - - - |
end            | - - - - - - - A B C |
center         | - - - A B C - - - - |
spaceBetween   | A - - - B - - - - C |
spaceAround    | - A - - B - - C - - |
spaceEvenly    | - A - B - C - |
    

CrossAxisAlignment options


start    : همه به ابتدای cross axis می‌چسبند
end      : همه به انتها
center   : وسط
stretch  : به اندازه parent کشیده می‌شوند
baseline : خط پایه متن (فقط برای متن)
    

۴.۳ Expanded و Flexible


// Expanded: کل فضای باقی‌مانده را اشغال می‌کند
Row(
  children: [
    Container(width: 100, color: Colors.red),
    Expanded(  // ← فضای باقی‌مانده
      child: Container(color: Colors.green),
    ),
    Container(width: 100, color: Colors.blue),
  ],
)

// چند Expanded با flex
Row(
  children: [
    Expanded(flex: 1, child: Container(color: Colors.red)),    // 25%
    Expanded(flex: 2, child: Container(color: Colors.green)),  // 50%
    Expanded(flex: 1, child: Container(color: Colors.blue)),   // 25%
  ],
)

// Flexible: حداکثر اندازه می‌گیرد ولی می‌تواند کمتر باشد
Row(
  children: [
    Flexible(
      child: Text("متن طولانی که اگر فضا نباشد wrap می‌شود..."),
    ),
    Icon(Icons.star),
  ],
)
    
خطای رایج: “RenderFlex overflowed by X pixels” یعنی محتوا از parent بزرگ‌تر است. راه‌حل: استفاده از Expanded، Flexible یا SingleChildScrollView.

۴.۴ Stack — overlay widgets


Stack(
  alignment: Alignment.center,
  children: [
    // پایین‌ترین لایه
    Container(
      width: 200,
      height: 200,
      color: Colors.blue,
    ),
    // وسط
    Container(
      width: 100,
      height: 100,
      color: Colors.red,
    ),
    // بالاترین لایه
    const Icon(Icons.star, size: 50, color: Colors.white),
  ],
)

// Positioned برای موقعیت دقیق
Stack(
  children: [
    Image.network("..."),
    
    // متن در پایین چپ
    Positioned(
      bottom: 16,
      left: 16,
      child: Text("عنوان", style: TextStyle(color: Colors.white)),
    ),
    
    // badge در گوشه بالا راست
    Positioned(
      top: 8,
      right: 8,
      child: CircleAvatar(
        backgroundColor: Colors.red,
        child: Text("3"),
      ),
    ),
  ],
)
    

۴.۵ ListView و GridView

ListView — لیست قابل اسکرول


// 1. ListView ساده (children محدود)
ListView(
  padding: const EdgeInsets.all(16),
  children: const [
    Text("آیتم ۱"),
    Text("آیتم ۲"),
    Text("آیتم ۳"),
  ],
)

// 2. ListView.builder (lazy loading - برای لیست‌های بزرگ)
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    final item = items[index];
    return ListTile(
      leading: CircleAvatar(child: Text("${index + 1}")),
      title: Text(item.title),
      subtitle: Text(item.subtitle),
      trailing: const Icon(Icons.arrow_back),  // ← در RTL
      onTap: () => print("کلیک $index"),
    );
  },
)

// 3. ListView.separated — با جدا کننده
ListView.separated(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(title: Text(items[index].name)),
  separatorBuilder: (context, index) => const Divider(),
)

// 4. ListView افقی
SizedBox(
  height: 100,
  child: ListView(
    scrollDirection: Axis.horizontal,
    children: [
      Card(child: SizedBox(width: 100, child: Text("1"))),
      Card(child: SizedBox(width: 100, child: Text("2"))),
      Card(child: SizedBox(width: 100, child: Text("3"))),
    ],
  ),
)
    

GridView — شبکه


// 1. GridView.count (تعداد ستون ثابت)
GridView.count(
  crossAxisCount: 2,
  mainAxisSpacing: 16,
  crossAxisSpacing: 16,
  padding: const EdgeInsets.all(16),
  children: List.generate(20, (i) => Card(
    child: Center(child: Text("آیتم $i")),
  )),
)

// 2. GridView.builder (lazy)
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 0.75,  // نسبت width/height
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ProductCard(product: products[index]);
  },
)

// 3. با اندازه پویا (مثل Pinterest)
GridView.builder(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200,  // حداکثر عرض هر آیتم
    mainAxisSpacing: 8,
    crossAxisSpacing: 8,
  ),
  itemCount: items.length,
  itemBuilder: (context, index) => ItemCard(item: items[index]),
)
    

۴.۶ Sizing و Spacing


// SizedBox — اندازه ثابت (یا spacer)
const SizedBox(width: 16, height: 16)
const SizedBox(height: 20)  // فقط برای فاصله عمودی
const SizedBox.shrink()      // 0x0
const SizedBox.expand()      // پر کردن کل parent

// Padding
const Padding(
  padding: EdgeInsets.all(16),
  child: Text("متن"),
)

// EdgeInsets variants
EdgeInsets.all(16)
EdgeInsets.symmetric(horizontal: 16, vertical: 8)
EdgeInsets.only(top: 16, left: 8)
EdgeInsets.fromLTRB(8, 16, 8, 16)

// Center — مرکز کردن child
const Center(child: Text("وسط"))

// Align — جای دقیق child
const Align(
  alignment: Alignment.bottomRight,
  child: Text("پایین راست"),
)

// AspectRatio
AspectRatio(
  aspectRatio: 16 / 9,
  child: Container(color: Colors.blue),
)

// FractionallySizedBox
FractionallySizedBox(
  widthFactor: 0.5,  // 50% عرض parent
  heightFactor: 0.3,
  child: Container(color: Colors.red),
)

// ConstrainedBox
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 100,
    maxWidth: 300,
    minHeight: 50,
    maxHeight: 200,
  ),
  child: Text("constrained"),
)
    

۴.۷ Material vs Cupertino

Flutter دو زبان طراحی built-in دارد:

Material Design

طراحی Google برای Android (و وب). MaterialApp، Scaffold، AppBar، FloatingActionButton.

Cupertino

طراحی Apple برای iOS. CupertinoApp، CupertinoNavigationBar، CupertinoButton.


// Material
MaterialApp(
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
    useMaterial3: true,
  ),
  home: Scaffold(
    appBar: AppBar(title: const Text("Material")),
    body: Center(child: ElevatedButton(onPressed: () {}, child: const Text("کلیک"))),
  ),
)

// Cupertino
CupertinoApp(
  theme: const CupertinoThemeData(primaryColor: CupertinoColors.systemBlue),
  home: CupertinoPageScaffold(
    navigationBar: const CupertinoNavigationBar(middle: Text("Cupertino")),
    child: Center(
      child: CupertinoButton.filled(
        onPressed: () {},
        child: const Text("کلیک"),
      ),
    ),
  ),
)

// Adaptive — انتخاب خودکار بر اساس platform
import "package:flutter/foundation.dart";

Widget getButton() {
  if (defaultTargetPlatform == TargetPlatform.iOS) {
    return CupertinoButton(onPressed: () {}, child: const Text("OK"));
  }
  return ElevatedButton(onPressed: () {}, child: const Text("OK"));
}
    

۴.۸ طراحی Responsive (واکنش‌گرا)


// 1. MediaQuery — اطلاعات صفحه
final size = MediaQuery.of(context).size;
final isTablet = size.width > 600;

if (isTablet) {
  return TabletLayout();
} else {
  return MobileLayout();
}

// 2. LayoutBuilder — بر اساس parent constraints
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return Row(children: [...]);
    }
    return Column(children: [...]);
  },
)

// 3. OrientationBuilder
OrientationBuilder(
  builder: (context, orientation) {
    return GridView.count(
      crossAxisCount: orientation == Orientation.portrait ? 2 : 4,
      children: items,
    );
  },
)

// 4. SafeArea — اجتناب از notch، status bar
SafeArea(
  child: Scaffold(
    body: Text("داخل safe area"),
  ),
)

// 5. flutter_screenutil package برای adaptive sizing
// در pubspec: flutter_screenutil: ^5.9.0

import "package:flutter_screenutil/flutter_screenutil.dart";

@override
Widget build(BuildContext context) {
  return ScreenUtilInit(
    designSize: const Size(360, 690),
    builder: (context, child) {
      return MaterialApp(home: HomePage());
    },
  );
}

// در widget ها
Container(
  width: 200.w,           // واکنش‌گرا
  height: 100.h,
  margin: EdgeInsets.all(16.w),
  child: Text("متن", style: TextStyle(fontSize: 14.sp)),
)
    

۴.۹ Theming و Styling


// تعریف تم
final theme = ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: const Color(0xFF02569B),  // Flutter blue
    brightness: Brightness.light,
  ),
  useMaterial3: true,
  
  // Typography
  fontFamily: "Vazirmatn",
  textTheme: const TextTheme(
    headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
    headlineMedium: TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
    bodyLarge: TextStyle(fontSize: 16),
    bodyMedium: TextStyle(fontSize: 14),
  ),
  
  // Component themes
  elevatedButtonTheme: ElevatedButtonThemeData(
    style: ElevatedButton.styleFrom(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    ),
  ),
  
  cardTheme: CardTheme(
    elevation: 2,
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  ),
  
  inputDecorationTheme: InputDecorationTheme(
    border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
    contentPadding: const EdgeInsets.all(16),
  ),
);

// Dark theme
final darkTheme = ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.deepPurple,
    brightness: Brightness.dark,
  ),
  useMaterial3: true,
);

// در MaterialApp
MaterialApp(
  theme: theme,
  darkTheme: darkTheme,
  themeMode: ThemeMode.system,  // system, light, dark
  home: HomePage(),
)

// استفاده از theme در widget ها
Text(
  "عنوان",
  style: Theme.of(context).textTheme.headlineMedium,
)

Container(
  color: Theme.of(context).colorScheme.primary,
  child: Text(
    "متن",
    style: TextStyle(color: Theme.of(context).colorScheme.onPrimary),
  ),
)
    

۴.۱۰ پشتیبانی RTL برای فارسی


MaterialApp(
  // فعال‌سازی localization
  localizationsDelegates: const [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: const [
    Locale("fa", "IR"),
    Locale("en", "US"),
  ],
  locale: const Locale("fa", "IR"),
  
  // فونت فارسی
  theme: ThemeData(
    fontFamily: "Vazirmatn",
    textTheme: GoogleFonts.vazirmatnTextTheme(),
  ),
  
  home: const HomePage(),
)

// Force direction در یک قسمت
Directionality(
  textDirection: TextDirection.rtl,
  child: Column(
    children: const [
      Text("راست به چپ"),
    ],
  ),
)

// با pubspec و asset
// pubspec.yaml:
flutter:
  fonts:
    - family: Vazirmatn
      fonts:
        - asset: assets/fonts/Vazirmatn-Regular.ttf
        - asset: assets/fonts/Vazirmatn-Bold.ttf
          weight: 700
    

۴.۱۱ بهترین تجربیات Layout

  1. از const استفاده کنید. برای SizedBox، EdgeInsets، Text const.
  2. Expanded و Flexible برای overflow.
  3. SingleChildScrollView برای محتوای متغیر.
  4. SafeArea در Scaffold. notch و status bar.
  5. LayoutBuilder برای responsive.
  6. ListView.builder برای لیست بزرگ. lazy loading.
  7. Theme و TextTheme. hardcode style ها نکنید.
  8. const Color برای رنگ‌ها.
  9. Spacing ثابت. 4، 8، 16، 24 (تقسیم بر 8).
  10. RTL support. از روز اول.

۴.۱۲ خلاصه فصل

آنچه آموختیم:
  • Row و Column با MainAxisAlignment و CrossAxisAlignment
  • Expanded و Flexible برای فضای پویا
  • Stack و Positioned برای overlay
  • ListView.builder و GridView.builder برای لیست بزرگ
  • Sizing: SizedBox، Padding، Center، AspectRatio
  • Material vs Cupertino
  • طراحی Responsive با MediaQuery و LayoutBuilder
  • Theming کامل با ThemeData
  • RTL برای فارسی با Vazirmatn
در فصل بعد: ناوبری و Routing با Navigator 1.0/2.0، go_router و deep linking.

نمایش سایت

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

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