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-tools
component 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' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: ^3.1.1
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.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(
useMaterial3: true,
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(
useMaterial3: true,
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
میپیچد.
موضوع و سبک
برای اینکه کارت بیشتر به چشم بیاید، آن را با رنگ غنی تر رنگ کنید. و از آنجایی که همیشه حفظ یک طرح رنگ ثابت ایده خوبی است، از 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
استفاده کنید. یک ویجت مفید که در هر کجا که قرار دهید یک مستطیل متقاطع می کشد و آن قسمت از رابط کاربری را به عنوان ناتمام علامت گذاری می کند.
- با استفاده از اصل سریع شکست ، دستور switch همچنین مطمئن میشود که اگر
selectedIndex
0 یا 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 دنبال کنید.