1. مقدمه
Flutter ابزار UI گوگل برای ساخت برنامه های کاربردی برای موبایل، وب و دسکتاپ از یک پایگاه کد واحد است. در این نرم افزار کد، اپلیکیشن فلاتر زیر را خواهید ساخت:
این برنامه نامهای جذابی مانند «newstay»، «lightstream»، «mainbrake» یا «greypine» تولید میکند. کاربر می تواند نام بعدی را بپرسد، نام فعلی را مورد علاقه خود قرار دهد، و لیست نام های مورد علاقه را در صفحه ای جداگانه بررسی کند. این برنامه به اندازه های مختلف صفحه نمایش پاسخگو است.
چیزی که یاد خواهید گرفت
- اصول اولیه نحوه عملکرد فلاتر
- ایجاد طرح بندی در فلاتر
- اتصال تعاملات کاربر (مانند فشار دادن دکمه) به رفتار برنامه
- سازماندهی کد Flutter خود را حفظ کنید
- ریسپانسیو کردن برنامه شما (برای صفحات مختلف)
- دستیابی به ظاهر و احساس سازگار از برنامه شما
شما با یک داربست اولیه شروع می کنید تا بتوانید مستقیماً به قسمت های جالب بپرید.

و در اینجا فیلیپ شما را از طریق کل کدها می برد!
برای شروع آزمایشگاه روی next کلیک کنید.
2. محیط Flutter خود را تنظیم کنید
ویرایشگر
برای سادهتر کردن این نرمافزار، فرض میکنیم که از کد ویژوال استودیو (VS Code) بهعنوان محیط توسعه خود استفاده میکنید. این رایگان است و بر روی تمام پلتفرم های اصلی کار می کند.
البته استفاده از هر ویرایشگری که دوست دارید خوب است: Android Studio، سایر IntelliJ IDE، Emacs، Vim، یا Notepad++. همه آنها با فلاتر کار می کنند.
توصیه میکنیم از VS Code برای این کد لبه استفاده کنید زیرا دستورالعملها بهطور پیشفرض روی میانبرهای ویژه کد VS هستند. گفتن جملاتی مانند "اینجا کلیک کنید" یا "این کلید را فشار دهید" به جای عبارتی مانند "عمل مناسب را در ویرایشگر خود برای انجام X انجام دهید" آسان تر است.

یک هدف توسعه انتخاب کنید
Flutter یک جعبه ابزار چند پلتفرمی است. برنامه شما می تواند روی هر یک از سیستم عامل های زیر اجرا شود:
- iOS
- اندروید
- ویندوز
- macOS
- لینوکس
- وب
با این حال، انتخاب یک سیستم عامل واحد که در درجه اول برای آن توسعه می دهید، معمول است. این "هدف توسعه" شما است - سیستم عاملی که برنامه شما در طول توسعه روی آن اجرا می شود.

به عنوان مثال، فرض کنید از یک لپ تاپ ویندوزی برای توسعه یک برنامه Flutter استفاده می کنید. اگر Android را به عنوان هدف توسعه خود انتخاب کنید، معمولاً یک دستگاه Android را با کابل USB به لپتاپ ویندوز خود متصل میکنید و برنامه در حال توسعه شما بر روی آن دستگاه Android متصل اجرا میشود. اما شما همچنین می توانید ویندوز را به عنوان هدف توسعه انتخاب کنید، به این معنی که برنامه در حال توسعه شما به عنوان یک برنامه ویندوز در کنار ویرایشگر شما اجرا می شود.
ممکن است وسوسه انگیز باشد که وب را به عنوان هدف توسعه خود انتخاب کنید. نقطه ضعف این انتخاب این است که یکی از مفیدترین ویژگی های توسعه Flutter را از دست می دهید: Stateful Hot Reload. Flutter نمی تواند برنامه های وب را دوباره بارگیری کند.
اکنون انتخاب خود را انجام دهید. به یاد داشته باشید: همیشه می توانید برنامه خود را بعداً روی سایر سیستم عامل ها اجرا کنید. فقط این است که داشتن یک هدف توسعه واضح در ذهن قدم بعدی را هموارتر می کند.
فلاتر را نصب کنید
به روزترین دستورالعمل ها در مورد نحوه نصب Flutter SDK همیشه در docs.flutter.dev موجود است.
دستورالعملهای وبسایت Flutter نه تنها نصب خود SDK، بلکه ابزارهای مرتبط با هدف توسعه و افزونههای ویرایشگر را نیز پوشش میدهد. به یاد داشته باشید که برای این کد لبه، فقط باید موارد زیر را نصب کنید:
- فلوتر SDK
- کد ویژوال استودیو با افزونه Flutter
- نرم افزار مورد نیاز هدف توسعه انتخابی شما (به عنوان مثال: Visual Studio برای هدف قرار دادن ویندوز، یا Xcode برای هدف قرار دادن macOS)
در بخش بعدی، اولین پروژه فلاتر خود را ایجاد خواهید کرد.
اگر تاکنون با مشکلاتی روبرو بوده اید، ممکن است برخی از این پرسش ها و پاسخ ها (از StackOverflow) برای عیب یابی مفید باشد.
سوالات متداول
- چگونه مسیر Flutter SDK را پیدا کنم؟
- وقتی دستور Flutter پیدا نشد چه کار کنم؟
- چگونه می توانم مشکل "در انتظار یک فرمان فلاتر دیگر برای آزاد کردن قفل راه اندازی" را برطرف کنم؟
- چگونه به Flutter بگویم که نصب Android SDK من کجاست؟
- چگونه با خطای جاوا هنگام اجرای
flutter doctor --android-licensesمقابله کنم؟ - چگونه با ابزار Android
sdkmanagerپیدا نشد برخورد کنم؟ - چگونه با خطای "
cmdline-toolscomponent is missing" برخورد کنم؟ - چگونه CocoaPods را روی Apple Silicon (M1) اجرا کنم؟
- چگونه می توانم فرمت خودکار را در ذخیره در VS Code غیرفعال کنم؟
3. یک پروژه ایجاد کنید
اولین پروژه فلاتر خود را ایجاد کنید
Visual Studio Code را اجرا کنید و پالت فرمان را باز کنید (با F1 یا Ctrl+Shift+P یا Shift+Cmd+P ). شروع به تایپ "flutter new" کنید. دستور Flutter: New Project را انتخاب کنید.
سپس، Application و سپس پوشه ای را انتخاب کنید که در آن پروژه خود را ایجاد کنید. این می تواند فهرست اصلی شما یا چیزی شبیه به C:\src\ باشد.
در نهایت نام پروژه خود را بنویسید. چیزی مانند namer_app یا my_awesome_namer .

Flutter اکنون پوشه پروژه شما را ایجاد می کند و VS Code آن را باز می کند.
اکنون محتویات 3 فایل را با یک داربست اولیه برنامه بازنویسی خواهید کرد.
برنامه اولیه را کپی و جایگذاری کنید
در قسمت سمت چپ VS Code، مطمئن شوید که Explorer انتخاب شده است و فایل pubspec.yaml باز کنید.

محتوای این فایل را با موارد زیر جایگزین کنید:
pubspec.yaml
name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0
environment:
sdk: ^3.9.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
فایل pubspec.yaml اطلاعات اولیه برنامه شما را مشخص میکند، مانند نسخه فعلی، وابستگیهای آن، و داراییهایی که با آن ارسال میشود.
سپس، فایل پیکربندی دیگری را در پروژه باز کنید، analysis_options.yaml .

محتوای آن را با موارد زیر جایگزین کنید:
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
prefer_const_constructors_in_immutables: false
prefer_const_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_final_fields: false
unnecessary_breaks: true
use_key_in_widget_constructors: false
این فایل تعیین می کند که فلاتر در هنگام تجزیه و تحلیل کد شما چقدر باید سخت باشد. از آنجایی که این اولین حمله شما به فلاتر است، به تحلیلگر میگویید که آن را راحت کند. همیشه می توانید بعداً این را تنظیم کنید. در واقع، با نزدیکتر شدن به انتشار یک برنامه تولید واقعی، تقریباً مطمئناً میخواهید که تحلیلگر را سختتر کنید.
در نهایت فایل main.dart را در زیر پوشه lib/ باز کنید.

محتوای این فایل را با موارد زیر جایگزین کنید:
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [Text('A random idea:'), Text(appState.current.asLowerCase)],
),
);
}
}
این 50 خط کد تا کنون کل برنامه است.
در قسمت بعدی برنامه را در حالت دیباگ اجرا کرده و شروع به توسعه کنید.
4. یک دکمه اضافه کنید
این مرحله یک دکمه Next را برای ایجاد یک جفت کلمه جدید اضافه می کند.
برنامه را اجرا کنید
ابتدا lib/main.dart را باز کنید و مطمئن شوید که دستگاه مورد نظر خود را انتخاب کرده اید. در گوشه سمت راست پایین VS Code، دکمه ای را خواهید دید که دستگاه مورد نظر فعلی را نشان می دهد. برای تغییر آن کلیک کنید.
وقتی lib/main.dart باز است، «play» را پیدا کنید
در گوشه سمت راست بالای پنجره VS Code را فشار دهید و روی آن کلیک کنید.
پس از حدود یک دقیقه، برنامه شما در حالت اشکال زدایی راه اندازی می شود. هنوز خیلی به نظر نمی رسد:

اولین بارگذاری مجدد داغ
در پایین lib/main.dart ، چیزی به رشته در اولین شیء Text اضافه کنید و فایل را ذخیره کنید (با Ctrl+S یا Cmd+S ). به عنوان مثال:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
توجه کنید که چگونه برنامه بلافاصله تغییر می کند اما کلمه تصادفی یکسان می ماند. این همان Hot Reload معروف فلاتر در محل کار است. هنگامی که تغییرات را در یک فایل منبع ذخیره می کنید، بارگذاری مجدد داغ فعال می شود.
سوالات متداول
- اگر Hot Reload در VSCode کار نکند چه؟
- آیا برای بارگذاری مجدد داغ در VSCode باید 'r' را فشار دهم؟
- آیا Hot Reload در وب کار می کند؟
- چگونه بنر "Debug" را حذف کنم؟
اضافه کردن دکمه
سپس، یک دکمه در پایین Column ، درست زیر نمونه دوم Text اضافه کنید.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
وقتی تغییر را ذخیره میکنید، برنامه دوباره بهروزرسانی میشود: یک دکمه ظاهر میشود و وقتی روی آن کلیک میکنید، کنسول Debug در VS Code دکمهای را نشان میدهد که فشار داده شده است! پیام
یک دوره تصادف فلاتر در 5 دقیقه
به همان اندازه که تماشای Debug Console سرگرم کننده است، می خواهید این دکمه کاری معنادارتر انجام دهد. با این حال، قبل از رسیدن به آن، نگاهی دقیقتر به کد موجود در lib/main.dart بیندازید تا نحوه عملکرد آن را درک کنید.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
در بالای فایل، تابع main() خواهید دید. در شکل فعلی، فقط به Flutter میگوید که برنامه تعریفشده در MyApp را اجرا کند.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
کلاس MyApp StatelessWidget را گسترش می دهد. ویجت ها عناصری هستند که هر اپلیکیشن فلاتر را از آنها می سازید. همانطور که می بینید، حتی خود برنامه نیز یک ویجت است.
کد موجود در MyApp کل برنامه را تنظیم می کند. حالت گسترده برنامه را ایجاد می کند (در ادامه در این مورد بیشتر توضیح می دهیم)، برنامه را نامگذاری می کند، تم بصری را تعریف می کند، و ویجت "home" - نقطه شروع برنامه شما را تنظیم می کند.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
در مرحله بعد، کلاس MyAppState وضعیت...خوب...وضعیت برنامه را تعریف می کند. این اولین تلاش شما در Flutter است، بنابراین این کد لبه آن را ساده و متمرکز نگه می دارد. راه های قدرتمند زیادی برای مدیریت وضعیت برنامه در فلاتر وجود دارد. یکی از سادهترین موارد توضیح ChangeNotifier است، رویکردی که این برنامه در پیش گرفته است.
-
MyAppStateداده هایی را که برنامه برای عملکرد به آن نیاز دارد تعریف می کند. در حال حاضر، فقط شامل یک متغیر با جفت کلمه تصادفی فعلی است. بعدا به این اضافه خواهید کرد. - کلاس state
ChangeNotifierگسترش می دهد، به این معنی که می تواند دیگران را در مورد تغییرات خود مطلع کند . برای مثال، اگر جفت کلمه فعلی تغییر کند، برخی از ویجتها در برنامه باید بدانند. - این حالت با استفاده از
ChangeNotifierProviderایجاد و به کل برنامه ارائه می شود (کد بالا را درMyAppببینید). این به هر ویجتی در برنامه اجازه می دهد تا وضعیت را در دست بگیرد.

lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) { // ← 1
var appState = context.watch<MyAppState>(); // ← 2
return Scaffold( // ← 3
body: Column( // ← 4
children: [
Text('A random AWESOME idea:'), // ← 5
Text(appState.current.asLowerCase), // ← 6
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
], // ← 7
),
);
}
}
// ...
در نهایت، MyHomePage وجود دارد، ویجتی که قبلاً آن را تغییر دادهاید. هر خط شماره گذاری شده زیر به یک نظر شماره خط در کد بالا نشان داده می شود:
- هر ویجت یک متد
build()را تعریف می کند که هر بار که شرایط ویجت تغییر می کند به طور خودکار فراخوانی می شود تا ویجت همیشه به روز باشد. -
MyHomePageتغییرات در وضعیت فعلی برنامه را با استفاده از روشwatchردیابی می کند. - هر روش
buildباید یک ویجت یا (به طور معمول) یک درخت تودرتو از ویجت ها را برگرداند. در این مورد، ویجت سطح بالاScaffoldاست. قرار نیست باScaffoldدر این Codelab کار کنید، اما یک ویجت مفید است و در اکثریت قریب به اتفاق برنامه های Flutter دنیای واقعی یافت می شود. -
Columnیکی از ابتدایی ترین ویجت های چیدمان در Flutter است. هر تعداد کودک را می گیرد و آنها را در یک ستون از بالا به پایین قرار می دهد. به طور پیش فرض، ستون به صورت بصری فرزندان خود را در بالا قرار می دهد. شما به زودی این را تغییر خواهید داد تا ستون در مرکز قرار گیرد. - شما این ویجت
Textرا در مرحله اول تغییر دادید. - این ویجت
TextدومappStateمی گیرد و به تنها عضو آن کلاس،current(که یکWordPairاست) دسترسی دارد.WordPairچندین دریافت کننده مفید مانندasPascalCaseیاasSnakeCaseارائه می دهد. در اینجا، ما ازasLowerCaseاستفاده میکنیم، اما اگر یکی از گزینهها را ترجیح میدهید، اکنون میتوانید آن را تغییر دهید. - توجه کنید که چگونه کد Flutter از کاماهای انتهایی استفاده زیادی می کند. این ویرگول خاص لازم نیست اینجا باشد، زیرا
childrenآخرین (و همچنین تنها ) عضو لیست پارامترهایColumnخاص هستند. با این حال، به طور کلی ایده خوبی است که از کاماهای انتهایی استفاده کنید: آنها اضافه کردن اعضای بیشتر را بی اهمیت می کنند، و همچنین به عنوان راهنمایی برای فرمت کننده خودکار Dart برای قرار دادن یک خط جدید در آنجا عمل می کنند. برای اطلاعات بیشتر، قالب بندی کد را ببینید.
بعد، دکمه را به حالت وصل خواهید کرد.
اولین رفتارت
به MyAppState بروید و یک متد getNext اضافه کنید.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
متد getNext() جدید current با یک WordPair تصادفی جدید دوباره اختصاص میدهد. همچنین notifyListeners() (روشی از ChangeNotifier) را فراخوانی میکند که تضمین میکند هر کسی که MyAppState تماشا میکند مطلع میشود.
تنها چیزی که باقی می ماند فراخوانی متد getNext از پاسخ تماس دکمه است.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
اکنون برنامه را ذخیره و امتحان کنید. هر بار که دکمه Next را فشار می دهید باید یک جفت کلمه تصادفی جدید ایجاد کند.
در بخش بعدی، رابط کاربری را زیباتر خواهید کرد.
5. اپلیکیشن را زیباتر کنید
برنامه در حال حاضر اینگونه است.

عالی نیست بخش مرکزی برنامه - جفت کلمات به طور تصادفی تولید شده - باید بیشتر قابل مشاهده باشد. به هر حال، این دلیل اصلی استفاده کاربران ما از این برنامه است! همچنین، محتویات برنامه به طرز عجیبی خارج از مرکز هستند و کل برنامه به طرز خسته کننده ای سیاه و سفید است.
این بخش با کار بر روی طراحی اپلیکیشن به این مسائل می پردازد. هدف نهایی برای این بخش چیزی شبیه به زیر است:

یک ویجت را استخراج کنید
خطی که مسئول نمایش جفت کلمه فعلی است اکنون به این شکل است: Text(appState.current.asLowerCase) . برای تغییر آن به چیزی پیچیده تر، ایده خوبی است که این خط را در یک ویجت جداگانه استخراج کنید. داشتن ویجتهای مجزا برای بخشهای منطقی مجزا از UI شما یک راه مهم برای مدیریت پیچیدگی در Flutter است.
Flutter یک کمک بازآفرینی برای استخراج ویجت ها ارائه می دهد، اما قبل از استفاده از آن، مطمئن شوید که خط استخراج شده فقط به آنچه نیاز دارد دسترسی دارد. در حال حاضر، خط به appState دسترسی دارد، اما واقعاً فقط باید بداند جفت کلمه فعلی چیست.
به همین دلیل، ویجت MyHomePage به صورت زیر بازنویسی کنید:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
خوبه ویجت Text دیگر به کل appState اشاره نمی کند.
اکنون منوی Refactor را فراخوانی کنید. در VS Code، این کار را به یکی از دو روش انجام می دهید:
- روی قطعه کدی که میخواهید refactor کنید کلیک راست کنید (در این مورد
Text) و Refactor... را از منوی کشویی انتخاب کنید.
یا
- مکان نما خود را به کد قطعه ای که می خواهید تغییر دهید (در این مورد
Text) ببرید وCtrl+.(Win/Linux) یاCmd+.(مک).
در منوی Refactor ، Extract Widget را انتخاب کنید. نامی مانند BigCard اختصاص دهید و روی Enter کلیک کنید.
این به طور خودکار یک کلاس جدید، BigCard ، در انتهای فایل فعلی ایجاد می کند. کلاس چیزی شبیه به زیر است:
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({super.key, required this.pair});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
توجه داشته باشید که برنامه حتی از طریق این refactoring چگونه به کار خود ادامه می دهد.
یک کارت اضافه کنید
اکنون زمان آن رسیده است که این ویجت جدید را به قطعه پررنگ UI که در ابتدای این بخش متصور بودیم تبدیل کنیم.
کلاس BigCard و متد build() را در آن پیدا کنید. مانند قبل، منوی Refactor را در ویجت Text فراخوانی کنید. با این حال، این بار قرار نیست ویجت را استخراج کنید.
در عوض، Wrap with Padding را انتخاب کنید. این یک ویجت والد جدید در اطراف ویجت Text به نام Padding ایجاد می کند. پس از ذخیره، خواهید دید که کلمه تصادفی از قبل فضای تنفس بیشتری دارد.
padding را از مقدار پیش فرض 8.0 افزایش دهید. به عنوان مثال، از چیزی حدود 20 برای بالشتک های جادارتر استفاده کنید.
بعد، یک سطح بالاتر بروید. مکان نما خود را روی ویجت Padding قرار دهید، منوی Refactor را بالا بکشید و Wrap with widget... را انتخاب کنید.
این به شما امکان می دهد ویجت والد را مشخص کنید. "Card" را تایپ کرده و Enter را فشار دهید.
این ویجت Padding و بنابراین Text با ویجت Card میپیچد.
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({super.key, required this.pair});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
}
// ...
اکنون برنامه چیزی شبیه به این خواهد بود:

موضوع و سبک
برای اینکه کارت بیشتر به چشم بیاید، آن را با رنگ غنی تر رنگ کنید. و از آنجایی که همیشه حفظ یک طرح رنگ ثابت ایده خوبی است، Theme برنامه برای انتخاب رنگ استفاده کنید.
تغییرات زیر را در متد build() BigCard اعمال کنید.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
این دو خط جدید کار زیادی انجام می دهند:
- ابتدا، کد موضوع فعلی برنامه را با
Theme.of(context)درخواست می کند. - سپس، کد رنگ کارت را با ویژگی
colorSchemeتم یکسان می کند. طرح رنگ شامل رنگ های زیادی است وprimaryترین رنگ مشخص برنامه است.
اکنون کارت با رنگ اصلی برنامه رنگ شده است:

می توانید این رنگ و طرح رنگ کل برنامه را با اسکرول کردن به MyApp و تغییر رنگ دانه برای ColorScheme در آنجا تغییر دهید.
توجه کنید که چگونه رنگ به آرامی متحرک می شود. این انیمیشن ضمنی نامیده می شود. بسیاری از ویجتهای Flutter به آرامی بین مقادیر درونیابی میکنند تا رابط کاربری فقط بین حالتها "پرش" نداشته باشد.
دکمه مرتفع زیر کارت نیز تغییر رنگ می دهد. این قدرت استفاده از یک Theme گسترده در برنامه است، برخلاف مقادیر کدگذاری سخت.
TextTheme
کارت هنوز مشکل دارد: متن خیلی کوچک است و رنگ آن به سختی خوانده می شود. برای رفع این مشکل، تغییرات زیر را در متد build() BigCard اعمال کنید.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
چه چیزی پشت این تغییر نهفته است:
- با استفاده از
theme.textTheme,به تم فونت برنامه دسترسی دارید. این کلاس شامل اعضایی مانندbodyMedium(برای متن استاندارد با اندازه متوسط)،caption(برای شرح تصاویر)، یاheadlineLarge(برای تیترهای بزرگ) است. - ویژگی
displayMediumیک سبک بزرگ برای نمایش متن است. کلمه نمایش در اینجا به معنای تایپوگرافی استفاده می شود، مانند تایپ فیس نمایش . مستنداتdisplayMediumمیگوید که «سبکهای نمایش برای متنهای کوتاه و مهم رزرو شدهاند» - دقیقاً مورد استفاده ما. - ویژگی
displayMediumتم از نظر تئوری می تواندnullباشد. دارت، زبان برنامه نویسی که در آن این برنامه را می نویسید، از نظر تهی ایمن است، بنابراین به شما اجازه نمی دهد متدهایی از اشیاء بالقوهnullرا فراخوانی کنید. اما در این مورد، می توانید از!اپراتور ("اپراتور بنگ") برای اطمینان از اینکه دارت می دانید چه کاری انجام می دهید. (در این موردdisplayMediumقطعاً تهی نیست . دلیل اینکه ما این را می دانیم فراتر از محدوده این نرم افزار کد است.) - فراخوانی
copyWith()درdisplayMediumیک کپی از سبک متن را با تغییراتی که شما تعریف کرده اید برمی گرداند. در این حالت، شما فقط رنگ متن را تغییر می دهید. - برای دریافت رنگ جدید، یک بار دیگر به تم برنامه دسترسی دارید. ویژگی
onPrimaryطرح رنگ، رنگی را تعریف میکند که برای استفاده در رنگ اصلی برنامه مناسب است.
اکنون برنامه باید چیزی شبیه به زیر باشد:

اگر دوست دارید، کارت را بیشتر عوض کنید. در اینجا چند ایده وجود دارد:
-
copyWith()به شما امکان می دهد تا در مورد سبک متن چیزهای بیشتری نسبت به رنگ تغییر دهید. برای دریافت لیست کامل ویژگی هایی که می توانید تغییر دهید، مکان نما خود را در داخل پرانتزcopyWith()قرار دهید وCtrl+Shift+Space(Win/Linux) یاCmd+Shift+Space(Mac) را بزنید. - به طور مشابه، می توانید اطلاعات بیشتری در مورد ویجت
Cardتغییر دهید. به عنوان مثال، می توانید سایه کارت را با افزایش مقدار پارامترelevationبزرگ کنید. - سعی کنید رنگ ها را آزمایش کنید. به غیر از
theme.colorScheme.primary،.secondary،.surfaceو هزاران مورد دیگر نیز وجود دارد. همه این رنگ ها معادل هایonPrimaryخود را دارند.
بهبود دسترسی
Flutter به طور پیش فرض برنامه ها را در دسترس قرار می دهد. برای مثال، هر برنامه Flutter به درستی تمام متن و عناصر تعاملی موجود در برنامه را روی صفحهخوانها مانند TalkBack و VoiceOver قرار میدهد.

با این حال، گاهی اوقات، مقداری کار مورد نیاز است. در مورد این برنامه، صفحهخوان ممکن است در تلفظ برخی از جفتهای کلمه ایجاد شده مشکل داشته باشد. در حالی که انسان ها در شناسایی این دو کلمه در cheaphead مشکلی ندارند، یک صفحه خوان ممکن است ph را در وسط کلمه به صورت f تلفظ کند.
یک راه حل این است که pair.asLowerCase با "${pair.first} ${pair.second}" جایگزین کنید. دومی از درون یابی رشته ای برای ایجاد یک رشته (مانند "cheap head" ) از دو کلمه موجود در pair استفاده می کند. استفاده از دو کلمه مجزا به جای یک کلمه مرکب باعث می شود که صفحه خوان ها آنها را به درستی شناسایی کنند و تجربه بهتری را برای کاربران کم بینا فراهم می کند.
با این حال، ممکن است بخواهید سادگی بصری pair.asLowerCase را حفظ کنید. از ویژگی semanticsLabel Text استفاده کنید تا محتوای بصری ویجت متن را با محتوای معنایی که برای صفحهخوانها مناسبتر است، لغو کنید:
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
اکنون، صفحهخوانها به درستی هر جفت کلمه تولید شده را تلفظ میکنند، اما رابط کاربری یکسان باقی میماند. این را در عمل با استفاده از صفحهخوان روی دستگاه خود امتحان کنید.
UI را در مرکز قرار دهید
اکنون که جفت کلمات تصادفی با استعداد بصری کافی ارائه شده است، وقت آن است که آن را در مرکز پنجره/صفحه نمایش برنامه قرار دهید.
ابتدا، به یاد داشته باشید که BigCard بخشی از یک Column است. بهطور پیشفرض، ستونها فرزندان خود را در بالا قرار میدهند، اما ما میتوانیم این را لغو کنیم. به متد build() MyHomePage بروید و تغییرات زیر را اعمال کنید:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
این کودکان را در داخل Column در امتداد محور اصلی (عمودی) آن متمرکز می کند.

بچه ها قبلاً در امتداد محور متقاطع ستون قرار دارند (به عبارت دیگر، آنها قبلاً به صورت افقی در مرکز قرار دارند). اما خود Column در مرکز Scaffold نیست. ما میتوانیم این موضوع را با استفاده از ابزارک بازرس تأیید کنیم.
خود ابزارک بازرس فراتر از محدوده این کد لبه است، اما میتوانید ببینید که وقتی Column برجسته میشود، کل عرض برنامه را اشغال نمیکند. فقط به اندازه نیاز فرزندانش فضای افقی را اشغال می کند.
شما فقط می توانید خود ستون را در مرکز قرار دهید. مکان نما را روی Column قرار دهید، منوی Refactor (با Ctrl+. یا Cmd+. ) را فراخوانی کنید و Wrap with Center را انتخاب کنید.
اکنون برنامه باید چیزی شبیه به زیر باشد:

اگر می خواهید، می توانید این را کمی تغییر دهید.
- میتوانید ویجت
TextبالایBigCardرا حذف کنید. می توان استدلال کرد که متن توصیفی ("یک ایده تصادفی عالی:") دیگر مورد نیاز نیست زیرا UI حتی بدون آن نیز منطقی است. و به این ترتیب تمیزتر است. - همچنین می توانید یک ویجت
SizedBox(height: 10)بینBigCardوElevatedButtonاضافه کنید. به این ترتیب، فاصله کمی بین دو ویجت وجود دارد. ویجتSizedBoxفقط فضا می گیرد و هیچ چیزی را به خودی خود ارائه نمی دهد. معمولاً برای ایجاد "شکاف" بصری استفاده می شود.
با تغییرات اختیاری، MyHomePage حاوی این کد است:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
و ظاهر برنامه به شکل زیر است:

در بخش بعدی، قابلیت دلخواه (یا "پسندیدن") کلمات تولید شده را اضافه خواهید کرد.
6. قابلیت اضافه کنید
این برنامه کار می کند، و حتی گاهی اوقات جفت کلمات جالبی را ارائه می دهد. اما هر زمان که کاربر روی Next کلیک کند، هر جفت کلمه برای همیشه ناپدید می شود. بهتر است راهی برای "به خاطر سپردن" بهترین پیشنهادها داشته باشید: مانند دکمه "پسندیدن".

منطق کسب و کار را اضافه کنید
به MyAppState بروید و کد زیر را اضافه کنید:
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
بررسی تغییرات:
- شما یک ویژگی جدید به
MyAppStateبه نامfavoritesاضافه کردید. این ویژگی با یک لیست خالی مقدار دهی اولیه می شود:[]. - شما همچنین مشخص کردهاید که فهرست فقط میتواند شامل جفتهای کلمه باشد:
<WordPair>[]، با استفاده از کلیات . این به قویتر شدن برنامه شما کمک میکند - اگر بخواهید چیزی غیر ازWordPairرا به آن اضافه کنید، Dart حتی از اجرای برنامه خودداری میکند. به نوبه خود، میتوانید از فهرستfavoritesاستفاده کنید و بدانید که هرگز نمیتوانید شیهای ناخواسته (مانندnull) در آنجا پنهان شوند.
- شما همچنین یک روش جدید به
toggleFavorite()اضافه کردید که یا جفت کلمه فعلی را از لیست علاقه مندی ها حذف می کند (اگر قبلاً وجود دارد)، یا آن را اضافه می کند (اگر هنوز وجود ندارد). در هر صورت، کدnotifyListeners();پس از آن
دکمه را اضافه کنید
با خارج شدن از «منطق تجاری»، زمان آن رسیده که دوباره روی رابط کاربری کار کنید. قرار دادن دکمه «پسندیدن» در سمت چپ دکمه «بعدی» به یک Row نیاز دارد. ویجت Row معادل افقی Column است که قبلاً مشاهده کردید.
ابتدا دکمه موجود را در یک Row بپیچید. به متد build() MyHomePage بروید، مکان نما را روی ElevatedButton قرار دهید، منوی Refactor را با Ctrl+. یا Cmd+. و Wrap with Row را انتخاب کنید.
وقتی ذخیره میکنید، متوجه میشوید که Row مشابه Column عمل میکند - به طور پیشفرض، فرزندان خود را به سمت چپ جمع میکند. ( Column فرزندان خود را به بالا جمع کرد.) برای رفع این مشکل، می توانید از همان روش قبلی استفاده کنید، اما با mainAxisAlignment . با این حال، برای اهداف آموزشی (یادگیری)، از mainAxisSize استفاده کنید. این به Row میگوید که تمام فضای افقی موجود را نگیرد.
تغییر زیر را انجام دهید:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
رابط کاربری به همان جایی که قبلا بود بازگشته است.

بعد، دکمه Like را اضافه کنید و آن را به toggleFavorite() متصل کنید. برای چالش، ابتدا سعی کنید این کار را خودتان انجام دهید، بدون اینکه به بلوک کد زیر نگاه کنید.

اگر این کار را دقیقاً به همان روشی که در زیر انجام می شود انجام ندهید، اشکالی ندارد. در واقع، نگران نماد قلب نباشید، مگر اینکه واقعاً یک چالش بزرگ بخواهید.
همچنین شکست خوردن کاملاً اشکالی ندارد - بالاخره این اولین ساعت شما با فلاتر است.

در اینجا یک راه برای اضافه کردن دکمه دوم به MyHomePage وجود دارد. این بار از سازنده ElevatedButton.icon() برای ایجاد یک دکمه با یک نماد استفاده کنید. و در بالای روش build ، بسته به اینکه جفت کلمه فعلی از قبل در موارد دلخواه است یا خیر، نماد مناسب را انتخاب کنید. همچنین به استفاده مجدد از SizedBox توجه کنید تا دو دکمه کمی از هم فاصله داشته باشند.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
برنامه باید به شکل زیر باشد:
متأسفانه کاربر نمی تواند موارد دلخواه را ببیند . زمان آن فرا رسیده است که یک صفحه نمایش جداگانه به برنامه خود اضافه کنیم. شما را در بخش بعدی می بینیم!
7. راه آهن ناوبری را اضافه کنید
اکثر برنامه ها نمی توانند همه چیز را در یک صفحه جا بدهند. این برنامه خاص احتمالا می تواند، اما برای اهداف آموزشی، شما می خواهید یک صفحه نمایش جداگانه برای موارد دلخواه کاربر ایجاد کنید. برای جابهجایی بین دو صفحه، میخواهید اولین StatefulWidget خود را پیادهسازی کنید.

برای اینکه هر چه زودتر به نتیجه این مرحله برسید، MyHomePage به 2 ویجت جداگانه تقسیم کنید.
همه MyHomePage را انتخاب کنید، آن را حذف کنید و کد زیر را جایگزین کنید:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
وقتی ذخیره شد، خواهید دید که قسمت بصری رابط کاربری آماده است — اما کار نمی کند. کلیک کردن روی ♥︎ (قلب) در ریل ناوبری کاری انجام نمی دهد.

تغییرات را بررسی کنید.
- ابتدا، توجه کنید که کل محتویات
MyHomePageدر یک ویجت جدید،GeneratorPageاستخراج شده است. تنها بخشی از ویجتMyHomePageقدیمی که استخراج نشدScaffoldاست. -
MyHomePageجدید حاوی یکRowبا دو فرزند است. اولین ویجتSafeAreaو دومی ویجتExpandedاست. -
SafeAreaتضمین میکند که فرزندش توسط یک بریدگی سختافزاری یا نوار وضعیت پنهان نشود. در این برنامه، ویجت دورNavigationRailمیپیچد تا مثلاً از پوشاندن دکمههای ناوبری توسط نوار وضعیت تلفن همراه جلوگیری کند. - می توانید خط
extended: falseرا درNavigationRailبهtrueتغییر دهید. این برچسب ها را در کنار آیکون ها نشان می دهد. در مرحله آینده، یاد خواهید گرفت که چگونه این کار را به طور خودکار زمانی که برنامه فضای افقی کافی دارد انجام دهید. - ریل ناوبری دارای دو مقصد ( خانه و موارد دلخواه )، با نمادها و برچسب های مربوطه است. همچنین
selectedIndexفعلی را تعریف می کند. یک شاخص انتخاب شده از صفر، مقصد اول را انتخاب می کند، یک شاخص انتخاب شده از یک، مقصد دوم را انتخاب می کند، و غیره. در حال حاضر، آن را به سختی به صفر کدگذاری شده است. - راه آهن ناوبری همچنین تعیین می کند که وقتی کاربر یکی از مقاصد را با
onDestinationSelectedانتخاب می کند چه اتفاقی می افتد. در حال حاضر، برنامه صرفاً مقدار شاخص درخواستی را باprint()خروجی میدهد. - فرزند دوم
RowویجتExpandedاست. ویجتهای گسترشیافته در ردیفها و ستونها بسیار مفید هستند - به شما اجازه میدهند طرحبندیهایی را بیان کنید که در آن برخی از کودکان تنها به اندازهای که فضای لازم را اشغال میکنند (SafeArea، در این مورد) و سایر ابزارکها باید تا جایی که ممکن است فضای باقیمانده را اشغال کنند (در این موردExpanded). یکی از راههای فکر کردن در مورد ویجتهایExpandedاین است که آنها "حریص" هستند. اگر میخواهید نقش این ویجت را بهتر درک کنید، ویجتSafeAreaرا باExpandedدیگری بپیچید. طرح به دست آمده چیزی شبیه به این است:

- دو ویجت
Expanded، تمام فضای افقی موجود را بین خود تقسیم میکنند، حتی اگر ریل ناوبری فقط به یک برش کوچک در سمت چپ نیاز داشته باشد. - در داخل ویجت
Expanded، یکContainerرنگی و در داخل ظرف،GeneratorPageوجود دارد.
ویجتهای بدون تابعیت در مقابل ویجتهای دارای حالت
تا به حال، MyAppState تمام نیازهای ایالتی شما را پوشش می داد. به همین دلیل است که تمام ویجت هایی که تا کنون نوشته اید دارای حالت کمتر هستند. آنها دارای هیچ حالت تغییرپذیری از خود نیستند. هیچ یک از ویجت ها نمی توانند خود را تغییر دهند - آنها باید از MyAppState عبور کنند.
این در حال تغییر است.
شما به روشی نیاز دارید تا مقدار selectedIndex ریل ناوبری را حفظ کنید. همچنین میخواهید بتوانید این مقدار را از درون callback onDestinationSelected تغییر دهید.
میتوانید selectedIndex به عنوان یکی دیگر از ویژگیهای MyAppState اضافه کنید. و کار می کرد. اما می توانید تصور کنید که اگر هر ویجت مقادیر خود را در آن ذخیره کند، وضعیت برنامه به سرعت فراتر از عقل رشد می کند.

برخی از حالت ها فقط مربوط به یک ویجت است، بنابراین باید با آن ویجت باقی بماند.
StatefulWidget را وارد کنید، نوعی ویجت که دارای State است. ابتدا MyHomePage به یک ویجت Stateful تبدیل کنید.
مکان نما را روی اولین خط MyHomePage قرار دهید (آنی که با class MyHomePage... شروع می شود)، و منوی Refactor را با استفاده از Ctrl+. یا Cmd+. . سپس، Convert to StatefulWidget را انتخاب کنید.
IDE یک کلاس جدید برای شما ایجاد می کند، _MyHomePageState . این کلاس State را گسترش می دهد و بنابراین می تواند مقادیر خود را مدیریت کند. (می تواند خود را تغییر دهد.) همچنین توجه کنید که متد build از ویجت قدیمی و بدون حالت به _MyHomePageState (به جای ماندن در ویجت) منتقل شده است. کلمه به کلمه منتقل شد - هیچ چیز در روش build تغییر نکرد. اکنون فقط در جای دیگری زندگی می کند.
setState
ویجت stateful جدید فقط باید یک متغیر را ردیابی کند: selectedIndex . 3 تغییر زیر را در _MyHomePageState اعمال کنید:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
بررسی تغییرات:
- شما یک متغیر جدید معرفی میکنید،
selectedIndexو آن را به0مقداردهی اولیه میکنید. - شما از این متغیر جدید در تعریف
NavigationRailبه جای0کدگذاری سختی که تا کنون وجود داشت استفاده می کنید. - هنگامی که پاسخ تماس
onDestinationSelectedفراخوانی می شود، به جای اینکه صرفاً مقدار جدید را در کنسول چاپ کنید، آن را در داخل یک فراخوانیsetState()بهselectedIndexاختصاص می دهید. این فراخوانی شبیه به متدnotifyListeners()است که قبلاً استفاده میشد - مطمئن میشود که رابط کاربری بهروزرسانی میشود.
ریل ناوبری اکنون به تعامل کاربر پاسخ می دهد. اما ناحیه گسترش یافته سمت راست ثابت می ماند. دلیلش این است که کد از selectedIndex برای تعیین اینکه چه صفحهای نمایش داده میشود استفاده نمیکند.
از selectIndex استفاده کنید
کد زیر را در بالای متد build _MyHomePageState ، درست قبل از return Scaffold قرار دهید:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
این قطعه کد را بررسی کنید:
- کد یک متغیر جدید،
page، از نوعWidgetرا اعلام می کند. - سپس، یک دستور switch یک صفحه را با توجه به مقدار فعلی در
selectedIndexبهpageاختصاص میدهد. - از آنجایی که هنوز
FavoritesPageوجود ندارد، ازPlaceholderاستفاده کنید. یک ویجت مفید که یک مستطیل متقاطع را در هر کجا که قرار دهید ، ترسیم می کند ، و آن قسمت از UI را ناتمام نشان می دهد.

- با استفاده از اصل Fail-Fast ، عبارت SWITCH همچنین اطمینان می دهد که اگر
selectedIndex0 یا 1 نباشد ، خطایی را به وجود می آورد. این به جلوگیری از اشکالات در خط کمک می کند. اگر تا به حال مقصد جدیدی را به راه آهن ناوبری اضافه کرده و فراموش نکنید که این کد را به روز کنید ، این برنامه در حال توسعه است (بر خلاف اینکه به شما اجازه می دهد حدس بزنید که چرا کارها کار نمی کند ، یا به شما امکان می دهد کد حشره ای را در تولید منتشر کنید).
اکنون آن page شامل ویجت است که می خواهید در سمت راست نشان دهید ، احتمالاً می توانید حدس بزنید که چه تغییر دیگری لازم است.
در اینجا _MyHomePageState پس از آن تغییر باقی مانده وجود دارد:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
اکنون این برنامه بین GeneratorPage ما و مکان نگهدارنده تغییر می کند که به زودی به صفحه مورد علاقه تبدیل می شود.
پاسخگویی
بعد ، راه آهن ناوبری را پاسخگو باشید. یعنی وقتی فضای کافی برای آنها وجود دارد ، آن را به طور خودکار برچسب ها (با استفاده از extended: true ) نشان دهید.

Flutter چندین ویجت را ارائه می دهد که به شما کمک می کند برنامه های خود را بطور خودکار پاسخ دهند. به عنوان مثال ، Wrap ویجت شبیه به Row یا Column است که به طور خودکار کودکان را به "خط" بعدی (به نام "اجرا") می پیچد وقتی فضای عمودی یا افقی کافی نباشد. FittedBox وجود دارد ، ویجت که به طور خودکار فرزند خود را با توجه به مشخصات شما در فضای موجود قرار می دهد.
اما NavigationRail وقتی فضای کافی وجود دارد ، به طور خودکار برچسب ها را نشان نمی دهد زیرا نمی تواند بداند فضای کافی در هر زمینه چیست . این به شما بستگی دارد ، توسعه دهنده ، این تماس را برقرار کنید.
بگویید شما تصمیم می گیرید برچسب ها را فقط در صورتی که MyHomePage حداقل 600 پیکسل باشد نشان دهید.
ویجت برای استفاده ، در این حالت ، LayoutBuilder است. این امکان را به شما می دهد تا بسته به فضای موجود خود ، درخت ویجت خود را تغییر دهید.
یک بار دیگر ، از منوی Repactor Flutter در VS Code استفاده کنید تا تغییرات مورد نیاز ایجاد شود. این بار ، با این حال ، کمی پیچیده تر است:
- در داخل روش
build_MyHomePageState، مکان نما خود را رویScaffoldقرار دهید. - منوی Refactor را با
Ctrl+.(ویندوز/لینوکس) یاCmd+.(مک). - بسته بندی را با سازنده انتخاب کنید و Enter را فشار دهید.
- نام
Builderتازه اضافه شده را بهLayoutBuilderتغییر دهید. - لیست پارامتر پاسخ به تماس را از
(context)به(context, constraints)تغییر دهید.
پاسخ دهنده builder LayoutBuilder هر بار که محدودیت ها تغییر می کنند فراخوانی می شود. این اتفاق می افتد که مثلاً:
- کاربر از پنجره برنامه تغییر اندازه می دهد
- کاربر تلفن خود را از حالت پرتره به حالت چشم انداز یا عقب می چرخاند
- برخی از ویجت ها در کنار
MyHomePageدر اندازه رشد می کنند و محدودیت هایMyHomePageرا کوچکتر می کنند
اکنون کد شما می تواند تصمیم بگیرد که آیا با پرس و جو از constraints فعلی ، برچسب را نشان دهید. تغییر یک خط زیر را به روش build _MyHomePageState تبدیل کنید:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
اکنون ، برنامه شما به محیط آن مانند اندازه صفحه نمایش ، جهت گیری و پلتفرم پاسخ می دهد! به عبارت دیگر ، پاسخگو است!.
تنها کاری که باقی مانده است جایگزین کردن آن Placeholder با یک صفحه نمایش مورد علاقه واقعی است. این در بخش بعدی پوشانده شده است.
8. یک صفحه جدید اضافه کنید
ویجت Placeholder به جای صفحه مورد علاقه به یاد دارید؟
وقت آن است که این مشکل را برطرف کنیم.
اگر احساس پرماجرا می کنید ، سعی کنید این کار را به صورت خود انجام دهید. هدف شما نشان دادن لیست favorites در یک ویجت بدون تابش جدید ، FavoritesPage و سپس نشان دادن آن ویجت به جای Placeholder است.
در اینجا چند نکته وجود دارد:
- هنگامی که می خواهید
Columnکه پیمایش می کند ، از ویجتListViewاستفاده کنید. - به یاد داشته باشید ، از هر ویجت با استفاده از
context.watch<MyAppState>()به نمونهMyAppStateدسترسی پیدا کنید. - اگر می خواهید ویجت جدیدی را امتحان کنید ،
ListTileدارای خواصی مانندtitle(به طور کلی برای متن) ،leading(برای نمادها یا آواتارها) وonTap(برای تعامل) است. با این حال ، شما می توانید با ابزارک هایی که قبلاً می شناسید به جلوه های مشابهی برسید. - DART اجازه می دهد
forحلقه های داخل مجموعه به معنای واقعی کلمه استفاده کنید. به عنوان مثال ، اگرmessagesشامل لیستی از رشته ها هستند ، می توانید کد مانند موارد زیر را داشته باشید:

از طرف دیگر ، اگر بیشتر با برنامه نویسی کاربردی آشنا هستید ، DART همچنین به شما امکان می دهد کد مانند messages.map((m) => Text(m)).toList() . و البته ، شما همیشه می توانید لیستی از ابزارک ها را ایجاد کنید و به طور ضروری به آن در روش build اضافه کنید.
مزیت اضافه کردن صفحه موارد دلخواه خودتان این است که با تصمیم گیری های خود بیشتر یاد می گیرید. نقطه ضعف این است که ممکن است شما دچار مشکل شوید که هنوز قادر به حل آن نیستید. به یاد داشته باشید: شکست خوب است و یکی از مهمترین عناصر یادگیری است. هیچ کس انتظار ندارد که شما در اولین ساعت خود پیشرفت خود را ناخن بزنید ، و نه شما.

آنچه در زیر می آید فقط یک راه برای اجرای صفحه موارد دلخواه است. نحوه اجرای آن (امیدوارم) شما را القا می کند که با کد بازی کنید - UI را بهبود بخشید و آن را به خود اختصاص دهید.
در اینجا کلاس جدید FavoritesPage :
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
در اینجا Widget انجام می دهد:
- این وضعیت فعلی برنامه را بدست می آورد.
- اگر لیست موارد دلخواه خالی باشد ، یک پیام محور را نشان می دهد: هنوز مورد علاقه های مورد علاقه نیست.
- در غیر این صورت ، یک لیست (قابل پیمایش) را نشان می دهد.
- لیست با خلاصه شروع می شود (به عنوان مثال ، شما 5 مورد علاقه دارید. ).
- کد سپس از طریق تمام موارد مورد علاقه تکرار می شود و یک ویجت
ListTileبرای هر یک ساخته می کند.
تمام آنچه اکنون باقی مانده است ، جایگزینی ویجت Placeholder با FavoritesPage است. و voilá!
می توانید کد نهایی این برنامه را در Repo CodeLab در GitHub دریافت کنید.
9. مراحل بعدی
تبریک می گویم!
به تو نگاه کن! شما یک داربست غیر کاربردی را با یک Column و دو ابزارک Text گرفتید و آن را به یک برنامه کوچک پاسخگو و لذت بخش تبدیل کردید.

آنچه ما پوشش داده ایم
- مبانی نحوه عملکرد Flutter
- ایجاد چیدمان در Flutter
- اتصال تعامل کاربر (مانند فشار دکمه) به رفتار برنامه
- نگه داشتن کد فلاتر خود
- پاسخگو بودن برنامه خود
- دستیابی به یک نگاه و احساس مداوم از برنامه خود
بعدش چی؟
- با برنامه ای که در طول این آزمایشگاه نوشتید بیشتر آزمایش کنید.
- به کد این نسخه پیشرفته از همان برنامه نگاه کنید تا ببینید که چگونه می توانید لیست های متحرک ، شیب ، محفظه های متقابل و موارد دیگر را اضافه کنید.
- سفر یادگیری خود را با رفتن به flutter.dev/learn دنبال کنید.