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
- از const استفاده کنید. برای SizedBox، EdgeInsets، Text const.
- Expanded و Flexible برای overflow.
- SingleChildScrollView برای محتوای متغیر.
- SafeArea در Scaffold. notch و status bar.
- LayoutBuilder برای responsive.
- ListView.builder برای لیست بزرگ. lazy loading.
- Theme و TextTheme. hardcode style ها نکنید.
- const Color برای رنگها.
- Spacing ثابت. 4، 8، 16، 24 (تقسیم بر 8).
- 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.