1. บทนำ
Flutter คือชุดเครื่องมือ UI ของ Google สำหรับการสร้างแอปพลิเคชันสำหรับอุปกรณ์เคลื่อนที่ เว็บ และเดสก์ท็อปจากฐานของโค้ดรายการเดียว ใน Codelab นี้ คุณจะได้สร้างแอปพลิเคชัน Flutter ต่อไปนี้
แอปพลิเคชันจะสร้างชื่อที่ฟังดูเท่ เช่น "newstay", "lightstream", "mainbrake" หรือ "graypine" ผู้ใช้สามารถขอชื่อถัดไป ตั้งชื่อปัจจุบันเป็นรายการโปรด และดูรายการชื่อที่ชื่นชอบในหน้าแยกต่างหาก แอปปรับเปลี่ยนตามขนาดหน้าจอต่างๆ
สิ่งที่คุณจะได้เรียนรู้
- ข้อมูลเบื้องต้นเกี่ยวกับวิธีการทำงานของ Flutter
- การสร้างเลย์เอาต์ใน Flutter
- เชื่อมโยงการโต้ตอบของผู้ใช้ (เช่น การกดปุ่ม) กับลักษณะการทํางานของแอป
- จัดระเบียบโค้ด Flutter
- การทําให้แอปปรับเปลี่ยนตามอุปกรณ์ (สําหรับหน้าจอต่างๆ)
- การสร้างรูปลักษณ์ของแอปให้สอดคล้องกัน
คุณจะเริ่มด้วยสคาฟเฟิลด์พื้นฐานเพื่อให้ข้ามไปยังส่วนที่สนใจได้ทันที
และ Filip จะพาคุณท่องไปทั่วทั้งโค้ดแล็บ
คลิกถัดไปเพื่อเริ่มใช้งานห้องทดลอง
2. ตั้งค่าสภาพแวดล้อม Flutter
ผู้แก้ไข
เราจะถือว่าคุณจะใช้ Visual Studio Code (VS Code) เป็นสภาพแวดล้อมการพัฒนาเพื่อให้ Codelab นี้เข้าใจง่ายที่สุด ซึ่งให้บริการฟรีและใช้งานได้บนแพลตฟอร์มหลักทั้งหมด
แต่คุณใช้เครื่องมือแก้ไขใดก็ได้ตามต้องการ ไม่ว่าจะเป็น Android Studio, IntelliJ IDE อื่นๆ, Emacs, Vim หรือ Notepad++ เครื่องมือเหล่านี้ใช้ได้กับ Flutter ทั้งหมด
เราขอแนะนำให้ใช้ VS Code สำหรับ Codelab นี้ เนื่องจากวิธีการเริ่มต้นด้วยแป้นพิมพ์ลัดเฉพาะของ VS Code การพูดว่า "คลิกที่นี่" หรือ "กดแป้นนี้" นั้นง่ายกว่าการพูดว่า "ดำเนินการที่เหมาะสมในเครื่องมือแก้ไขเพื่อดำเนินการ X"
เลือกเป้าหมายการพัฒนา
Flutter เป็นเครื่องมือแบบหลายแพลตฟอร์ม แอปของคุณจะทำงานได้ในระบบปฏิบัติการต่อไปนี้
- iOS
- Android
- Windows
- macOS
- Linux
- เว็บ
อย่างไรก็ตาม แนวทางปฏิบัติทั่วไปคือเลือกระบบปฏิบัติการเดียวที่จะพัฒนาเป็นหลัก ข้อมูลนี้คือ "เป้าหมายการพัฒนา" ซึ่งเป็นระบบปฏิบัติการที่แอปของคุณทำงานอยู่ในระหว่างการพัฒนา
ตัวอย่างเช่น สมมติว่าคุณใช้แล็ปท็อป Windows ในการพัฒนาแอป Flutter หากเลือก Android เป็นเป้าหมายการพัฒนา โดยทั่วไปคุณจะต้องต่ออุปกรณ์ Android กับแล็ปท็อป Windows ด้วยสาย USB และแอปที่กำลังพัฒนาจะทำงานบนอุปกรณ์ Android ที่ต่ออยู่ แต่คุณเลือก Windows เป็นเป้าหมายการพัฒนาได้เช่นกัน ซึ่งหมายความว่าแอปที่กำลังพัฒนาจะทำงานเป็นแอป Windows ควบคู่ไปกับเครื่องมือแก้ไข
คุณอาจเลือกเว็บเป็นเป้าหมายการพัฒนา ข้อเสียของตัวเลือกนี้คือคุณจะเสียสิทธิ์ใช้ฟีเจอร์การพัฒนาที่มีประโยชน์ที่สุดอย่างหนึ่งของ Flutter ซึ่งก็คือการโหลดซ้ำแบบมีสถานะ Flutter ไม่สามารถโหลดเว็บแอปพลิเคชันซ้ำขณะทำงานได้
เลือกเลย โปรดทราบว่าคุณสามารถเรียกใช้แอปในระบบปฏิบัติการอื่นๆ ในภายหลังได้เสมอ แต่การมีเป้าหมายการพัฒนาที่ชัดเจนจะช่วยให้ขั้นตอนถัดไปราบรื่นขึ้น
ติดตั้ง Flutter
วิธีการล่าสุดในการติดตั้ง Flutter SDK จะอยู่เสมอที่ docs.flutter.dev
วิธีการในเว็บไซต์ Flutter ครอบคลุมทั้งการติดตั้ง SDK เอง รวมถึงเครื่องมือที่เกี่ยวข้องกับเป้าหมายการพัฒนาและปลั๊กอินเครื่องมือแก้ไข โปรดทราบว่าสำหรับโค้ดแล็บนี้ คุณจะต้องติดตั้งสิ่งต่อไปนี้เท่านั้น
- Flutter SDK
- Visual Studio Code พร้อมปลั๊กอิน Flutter
- ซอฟต์แวร์ที่จําเป็นสําหรับเป้าหมายการพัฒนาที่เลือก (เช่น Visual Studio เพื่อกำหนดเป้าหมายเป็น Windows หรือ Xcode เพื่อกำหนดเป้าหมายเป็น macOS)
ในส่วนถัดไป คุณจะได้สร้างโปรเจ็กต์ Flutter โปรเจ็กต์แรก
หากพบปัญหาจนถึงตอนนี้ คุณอาจพบว่าคําถามและคําตอบเหล่านี้ (จาก StackOverflow) มีประโยชน์ในการแก้ปัญหา
คำถามที่พบบ่อย
- ฉันจะค้นหาเส้นทางของ Flutter SDK ได้อย่างไร
- ฉันควรทำอย่างไรเมื่อไม่พบคำสั่ง Flutter
- ฉันจะแก้ไขปัญหา "กำลังรอคําสั่ง Flutter อื่นเพื่อปลดล็อกการเริ่มต้น" ได้อย่างไร
- ฉันจะบอก Flutter ว่าการติดตั้ง Android SDK อยู่ที่ไหนได้อย่างไร
- ฉันจะจัดการกับข้อผิดพลาด Java เมื่อเรียกใช้
flutter doctor --android-licenses
ได้อย่างไร - ฉันจะจัดการกับข้อความ "ไม่พบเครื่องมือ
sdkmanager
" ของ Android ได้อย่างไร - ฉันจะจัดการกับข้อผิดพลาด "ไม่มีคอมโพเนนต์
cmdline-tools
" ได้อย่างไร - ฉันจะเรียกใช้ CocoaPods ใน Apple Silicon (M1) ได้อย่างไร
- ฉันจะปิดใช้การจัดรูปแบบอัตโนมัติเมื่อบันทึกใน VS Code ได้อย่างไร
3. สร้างโปรเจ็กต์
สร้างโปรเจ็กต์ Flutter โปรเจ็กต์แรก
เปิด Visual Studio Code แล้วเปิดแผงคำสั่ง (ใช้ F1
หรือ Ctrl+Shift+P
หรือ Shift+Cmd+P
) เริ่มพิมพ์ "flutter new" เลือกคำสั่ง Flutter: โปรเจ็กต์ใหม่
ถัดไป ให้เลือกแอปพลิเคชัน แล้วเลือกโฟลเดอร์ที่จะสร้างโปรเจ็กต์ ซึ่งอาจเป็นไดเรกทอรีบ้านหรือชื่ออย่าง 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.6.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.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
ไฟล์นี้จะกำหนดความเข้มงวดที่ Flutter ควรมีเมื่อวิเคราะห์โค้ด เนื่องจากนี่เป็นการใช้งาน Flutter ครั้งแรก คุณจึงบอกให้เครื่องมือวิเคราะห์ดำเนินการอย่างค่อยเป็นค่อยไป คุณจะปรับแต่งการตั้งค่านี้ในภายหลังได้ทุกเมื่อ อันที่จริงแล้ว เมื่อคุณใกล้จะเผยแพร่แอปเวอร์ชันที่ใช้งานจริง คุณจะต้องทำให้เครื่องมือวิเคราะห์เข้มงวดกว่านี้
สุดท้าย ให้เปิดไฟล์ 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. เพิ่มปุ่ม
ขั้นตอนนี้จะเพิ่มปุ่มถัดไปเพื่อสร้างการจับคู่คําใหม่
เปิดแอป
ก่อนอื่น ให้เปิด lib/main.dart
แล้วตรวจสอบว่าคุณได้เลือกอุปกรณ์เป้าหมายแล้ว คุณจะเห็นปุ่มที่แสดงอุปกรณ์เป้าหมายปัจจุบันที่มุมขวาล่างของ VS Code คลิกเพื่อเปลี่ยน
ขณะที่ lib/main.dart
เปิดอยู่ ให้หาปุ่ม "เล่น" ที่มุมขวาบนของหน้าต่าง VS Code แล้วคลิกปุ่มดังกล่าว
หลังจากผ่านไปประมาณ 1 นาที แอปจะเปิดในโหมดแก้ไขข้อบกพร่อง ดูเหมือนว่ายังไม่มีข้อมูลมากนัก
การโหลดซ้ำแบบ Hot ครั้งแรก
ที่ด้านล่างของ 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 แบบมีสถานะอันโด่งดังของ Flutter การโหลดซ้ำขณะทำงานจะเริ่มต้นเมื่อคุณบันทึกการเปลี่ยนแปลงในไฟล์ต้นฉบับ
คำถามที่พบบ่อย
- จะเกิดอะไรขึ้นหากการโหลดซ้ำขณะทำงานใน VSCode ไม่ทำงาน
- ฉันต้องกด "r" เพื่อโหลดซ้ำแบบ Hot Reload ใน VSCode ไหม
- การโหลดซ้ำขณะทำงานจะใช้งานได้บนเว็บไหม
- ฉันจะนำแบนเนอร์ "แก้ไขข้อบกพร่อง" ออกได้อย่างไร
การเพิ่มปุ่ม
ถัดไป ให้เพิ่มปุ่มที่ด้านล่างของ Column
ใต้อินสแตนซ์ Text
รายการที่ 2
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 Console ใน VS Code จะแสดงข้อความกดปุ่มแล้ว
หลักสูตรเร่งรัดเกี่ยวกับ Flutter ใน 5 นาที
แม้ว่าการดูคอนโซลการแก้ไขข้อบกพร่องจะสนุกมาก แต่คุณก็ต้องการให้ปุ่มทําอะไรที่มีประโยชน์มากกว่า แต่ก่อนที่จะดำเนินการดังกล่าว ให้ดูโค้ดใน 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
วิดเจ็ตคือองค์ประกอบที่คุณใช้สร้างแอป Flutter ทุกแอป ดังที่คุณเห็น แม้แต่แอปเองก็เป็นวิดเจ็ต
โค้ดใน MyApp
จะตั้งค่าทั้งแอป โดยจะสร้างสถานะทั่วทั้งแอป (ดูข้อมูลเพิ่มเติมในภายหลัง) ตั้งชื่อแอป กําหนดธีมภาพ และตั้งค่าวิดเจ็ต "หน้าแรก" ซึ่งเป็นจุดเริ่มต้นของแอป
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
ถัดไปคือคลาส MyAppState
ซึ่งกำหนดสถานะของแอป นี่เป็นครั้งแรกที่คุณลองใช้ Flutter ดังนั้น Codelab นี้จะเน้นไปที่เนื้อหาที่เข้าใจง่าย การจัดการสถานะแอปใน Flutter ทำได้หลายวิธี ตัวอย่างที่อธิบายได้ง่ายที่สุดคือ ChangeNotifier
ซึ่งเป็นแนวทางที่แอปนี้ใช้
MyAppState
กำหนดข้อมูลที่จำเป็นต่อการทำงานของแอป ขณะนี้มีเพียงตัวแปรเดียวที่มีคู่คําแบบสุ่มปัจจุบัน คุณจะเพิ่มข้อมูลในภายหลัง- คลาสสถานะจะขยาย
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
ในโค้ดแล็บนี้ แต่วิดเจ็ตนี้มีประโยชน์และพบในแอป Flutter ส่วนใหญ่ในชีวิตจริง Column
เป็นหนึ่งในวิดเจ็ตเลย์เอาต์พื้นฐานที่สุดใน Flutter โดยจะรับจำนวนรายการย่อยเท่าใดก็ได้และวางไว้ในคอลัมน์จากบนลงล่าง โดยค่าเริ่มต้น คอลัมน์จะวางองค์ประกอบย่อยไว้ที่ด้านบน คุณจะสามารถเปลี่ยนค่านี้ในเร็วๆ นี้เพื่อให้คอลัมน์อยู่ตรงกลาง- คุณเปลี่ยนวิดเจ็ต
Text
นี้ในขั้นตอนแรก - วิดเจ็ต
Text
ตัวที่ 2 นี้ใช้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
จาก Callback ของปุ่ม
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('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
ทั้งหมดอีกต่อไป
จากนั้นเปิดเมนูปรับโครงสร้าง ใน VS Code คุณดำเนินการนี้ได้ด้วย 2 วิธีดังนี้
- คลิกขวาที่โค้ดที่ต้องการปรับโครงสร้าง (ในกรณีนี้คือ
Text
) แล้วเลือกปรับโครงสร้าง... จากเมนูแบบเลื่อนลง
หรือ
- เลื่อนเคอร์เซอร์ไปยังโค้ดส่วนที่ต้องการปรับโครงสร้าง (ในกรณีนี้คือ
Text
) แล้วกดCtrl+.
(Win/Linux) หรือCmd+.
(Mac)
ในเมนูปรับโครงสร้าง ให้เลือกแยกวิดเจ็ต กําหนดชื่อ เช่น 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);
}
}
// ...
สังเกตว่าแอปยังคงทํางานได้แม้จะมีการแยกส่วนโค้ด
เพิ่มการ์ด
ตอนนี้ได้เวลาเปลี่ยนวิดเจ็ตใหม่นี้ให้เป็น UI ที่โดดเด่นตามที่เราได้จินตนาการไว้ตั้งแต่ต้นส่วนนี้แล้ว
ค้นหาคลาส BigCard
และเมธอด build()
ภายในคลาส เรียกเมนูปรับโครงสร้างในวิดเจ็ต Text
เช่นเดียวกับก่อนหน้านี้ แต่ครั้งนี้คุณจะไม่ได้ดึงข้อมูลวิดเจ็ต
แต่ให้เลือกตัดขึ้นบรรทัดใหม่โดยเพิ่มระยะห่างแทน ซึ่งจะสร้างวิดเจ็ตหลักใหม่รอบๆ วิดเจ็ต Text
ชื่อ Padding
หลังจากบันทึกแล้ว คุณจะเห็นคําแบบสุ่มมีระยะห่างมากขึ้น
เพิ่มระยะห่างจากขอบจากค่าเริ่มต้น 8.0
เช่น ใช้ 20
เพื่อให้มีระยะห่างมากขึ้น
ถัดไป ให้เลื่อนขึ้นอีก 1 ระดับ วางเคอร์เซอร์บนวิดเจ็ต Padding
ดึงเมนูปรับโครงสร้างขึ้นมา แล้วเลือกตัดขึ้นบรรทัดใหม่ด้วยวิดเจ็ต...
ซึ่งช่วยให้คุณระบุวิดเจ็ตหลักได้ พิมพ์ "การ์ด" แล้วกด 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),
),
);
}
// ...
บรรทัดใหม่ 2 บรรทัดนี้ทํางานได้หลายอย่าง
- ก่อนอื่น โค้ดจะขอธีมปัจจุบันของแอปด้วย
Theme.of(context)
- จากนั้นโค้ดจะกำหนดสีของการ์ดให้เหมือนกับพร็อพเพอร์ตี้
colorScheme
ของธีม รูปแบบสีมีสีหลายสี และprimary
เป็นสีที่โดดเด่นที่สุดซึ่งกำหนดลักษณะของแอป
ตอนนี้การ์ดจะทาสีด้วยสีหลักของแอป
คุณเปลี่ยนสีนี้และรูปแบบสีของแอปทั้งหมดได้โดยเลื่อนขึ้นไปยัง MyApp
แล้วเปลี่ยนสีเริ่มต้นของ ColorScheme
สังเกตว่าสีเคลื่อนไหวอย่างราบรื่นเพียงใด ซึ่งเรียกว่าภาพเคลื่อนไหวโดยนัย วิดเจ็ต Flutter หลายรายการจะหาค่าเฉลี่ยระหว่างค่าต่างๆ อย่างราบรื่นเพื่อให้ UI ไม่ "กระโดด" ระหว่างสถานะต่างๆ
ปุ่มที่ยกระดับใต้การ์ดจะเปลี่ยนสีด้วย นี่คือประโยชน์ของการใช้ 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
เป็นสไตล์ขนาดใหญ่สําหรับข้อความแสดงผล คําว่า display ใช้ในความหมายด้านการจัดรูปแบบตัวอักษร เช่น ในแบบอักษร Display เอกสารประกอบของdisplayMedium
ระบุว่า "สไตล์การแสดงผลสงวนไว้สำหรับข้อความสั้นๆ ที่สำคัญ" ซึ่งตรงกับกรณีการใช้งานของเรา - ในทางทฤษฎีแล้ว พร็อพเพอร์ตี้
displayMedium
ของธีมอาจเป็นnull
ได้ Dart ซึ่งเป็นภาษาโปรแกรมที่คุณใช้เขียนแอปนี้ไม่มีค่า Null จึงจะไม่อนุญาตให้คุณเรียกใช้เมธอดของออบเจ็กต์ที่อาจเป็นnull
ในกรณีนี้ คุณสามารถใช้โอเปอเรเตอร์!
("โอเปอเรเตอร์เครื่องหมายตกใจ") เพื่อบอก Dart ว่าคุณรู้สิ่งที่ทําอยู่ (displayMedium
ไม่ใช่ค่าว่างอย่างแน่นอนในกรณีนี้ เหตุผลที่เราทราบเรื่องนี้อยู่นอกขอบเขตของ Codelab นี้) - การเรียกใช้
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 อย่างถูกต้อง
แต่บางครั้งก็อาจต้องดำเนินการบางอย่าง ในกรณีของแอปนี้ โปรแกรมอ่านหน้าจออาจมีปัญหาในการออกเสียงคู่คำที่สร้างขึ้นบางคู่ แม้ว่ามนุษย์จะระบุคำ 2 คำใน cheaphead ได้ แต่โปรแกรมอ่านหน้าจออาจออกเสียง ph ตรงกลางคำเป็น f
วิธีที่ง่ายที่สุดคือแทนที่ pair.asLowerCase
ด้วย "${pair.first} ${pair.second}"
รูปแบบหลังใช้การแทรกสตริงเพื่อสร้างสตริง (เช่น "cheap head"
) จาก 2 คำที่อยู่ใน pair
การใช้ 2 คำแยกกันแทนคำประสมช่วยให้โปรแกรมอ่านหน้าจอระบุคำเหล่านั้นได้อย่างเหมาะสม และมอบประสบการณ์การใช้งานที่ดีขึ้นให้แก่ผู้ใช้ที่มีปัญหาด้านการมองเห็น
อย่างไรก็ตาม คุณอาจต้องคงภาพลักษณ์ของ 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 ยังคงเหมือนเดิม ลองใช้ฟีเจอร์นี้โดยใช้โปรแกรมอ่านหน้าจอในอุปกรณ์
วาง 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
แล้วเรียกเมนูปรับโครงสร้าง (ด้วย Ctrl+.
หรือ Cmd+.
) แล้วเลือกตัดขึ้นบรรทัดใหม่โดยจัดกึ่งกลาง
ตอนนี้แอปควรมีลักษณะดังต่อไปนี้
คุณปรับแต่งเพิ่มเติมได้หากต้องการ
- คุณสามารถนำวิดเจ็ต
Text
เหนือBigCard
ออกได้ อาจมีเหตุผลว่าไม่จำเป็นต้องมีข้อความอธิบาย ("A random AWESOME idea:") อีกต่อไปเนื่องจาก UI นั้นเข้าใจง่ายอยู่แล้ว และวิธีนี้จะทำให้ดูสะอาดตายิ่งขึ้น - นอกจากนี้ คุณยังเพิ่มวิดเจ็ต
SizedBox(height: 10)
ระหว่างBigCard
กับElevatedButton
ได้ด้วย วิธีนี้จะทำให้วิดเจ็ต 2 รายการแยกกันมากขึ้น วิดเจ็ต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. เพิ่มฟังก์ชันการทำงาน
แอปใช้งานได้และบางครั้งก็ให้คู่คำที่น่าสนใจ แต่ทุกครั้งที่ผู้ใช้คลิกถัดไป คู่คําแต่ละคู่จะหายไปอย่างถาวร ควรมีวิธี "จดจำ" คำแนะนำที่ดีที่สุด เช่น ปุ่ม "ชอบ"
เพิ่มตรรกะทางธุรกิจ
เลื่อนไปที่ 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();
}
}
// ...
ตรวจสอบการเปลี่ยนแปลง
- คุณได้เพิ่มพร็อพเพอร์ตี้ใหม่ชื่อ
favorites
ลงในMyAppState
พร็อพเพอร์ตี้นี้เริ่มต้นด้วยลิสต์ว่าง[]
- นอกจากนี้ คุณยังระบุด้วยว่ารายการต้องมีคู่คํา
<WordPair>[]
เท่านั้น โดยใช้ generics ซึ่งจะช่วยให้แอปมีความเสถียรมากขึ้น เนื่องจาก Dart จะไม่เรียกใช้แอปหากคุณพยายามเพิ่มสิ่งอื่นที่ไม่ใช่WordPair
ลงไป ในทางกลับกัน คุณสามารถใช้รายการfavorites
โดยที่มั่นใจได้ว่าจะไม่มีออบเจ็กต์ที่ไม่ต้องการ (เช่นnull
) ซ่อนอยู่
- นอกจากนี้ คุณยังเพิ่มเมธอดใหม่
toggleFavorite()
ซึ่งจะนําคู่คําปัจจุบันออกจากรายการรายการโปรด (หากมีอยู่แล้ว) หรือเพิ่มคู่คํานั้น (หากยังไม่มี) ไม่ว่าในกรณีใด โค้ดจะเรียกnotifyListeners();
ในภายหลัง
เพิ่มปุ่ม
เมื่อ "ตรรกะทางธุรกิจ" เสร็จสิ้นแล้ว ก็ถึงเวลาทำงานกับอินเทอร์เฟซผู้ใช้อีกครั้ง การวางปุ่ม "ชอบ" ทางด้านซ้ายของปุ่ม "ถัดไป" ต้องใช้ Row
วิดเจ็ต Row
เทียบเท่ากับ Column
ในแนวนอน ซึ่งคุณเห็นก่อนหน้านี้
ก่อนอื่น ให้ใส่ปุ่มที่มีอยู่ไว้ใน Row
ไปที่เมธอด build()
ของ MyHomePage
วางเคอร์เซอร์บน ElevatedButton
เปิดเมนูปรับโครงสร้างด้วย Ctrl+.
หรือ Cmd+.
แล้วเลือกตัดขึ้นบรรทัดใหม่
เมื่อบันทึกแล้ว คุณจะเห็นว่า Row
ทำงานคล้ายกับ Column
โดยค่าเริ่มต้น Row
จะรวมรายการย่อยไว้ทางด้านซ้าย (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'),
),
],
),
],
),
),
);
}
}
// ...
UI จะกลับไปเป็นเหมือนเดิม
ถัดไป ให้เพิ่มปุ่มชอบและเชื่อมต่อกับ toggleFavorite()
เพื่อเป็นการท้าทาย ให้ลองทำสิ่งนี้ด้วยตนเองก่อนโดยไม่ดูที่บล็อกโค้ดด้านล่าง
คุณไม่จำเป็นต้องทำตามขั้นตอนด้านล่างทุกประการ จริงๆ แล้ว คุณไม่ต้องกังวลเกี่ยวกับไอคอนหัวใจ เว้นแต่ว่าคุณต้องการความท้าทายครั้งใหญ่
และหากไม่สำเร็จก็ไม่เป็นไร เพราะนี่เป็นครั้งแรกที่คุณใช้ Flutter
ต่อไปนี้เป็นวิธีหนึ่งในการเพิ่มปุ่มที่ 2 ลงใน MyHomePage
ครั้งนี้ ให้ใช้คอนสตรคเตอร์ ElevatedButton.icon()
เพื่อสร้างปุ่มที่มีไอคอน และที่ด้านบนของวิธีการ build
ให้เลือกไอคอนที่เหมาะสม โดยขึ้นอยู่กับว่าคู่คําปัจจุบันอยู่ในรายการโปรดอยู่แล้วหรือไม่ และโปรดสังเกตการใช้ SizedBox
อีกครั้งเพื่อให้ปุ่ม 2 ปุ่มนี้อยู่ห่างกันเล็กน้อย
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. เพิ่มแถบข้างสำหรับไปยังส่วนต่างๆ
แอปส่วนใหญ่ไม่สามารถแสดงข้อมูลทั้งหมดในหน้าจอเดียว แอปนี้อาจทำได้ แต่คุณจะต้องสร้างหน้าจอแยกต่างหากสำหรับรายการโปรดของผู้ใช้เพื่อวัตถุประสงค์ในการสอน หากต้องการสลับระหว่าง 2 หน้าจอ คุณจะใช้ 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'),
),
],
),
],
),
);
}
}
// ...
เมื่อบันทึกแล้ว คุณจะเห็น UI เวอร์ชันภาพพร้อมใช้งาน แต่จะไม่ทำงาน การคลิก ♥︎ (หัวใจ) ในแถบนําทางจะไม่ทําอะไร
ตรวจสอบการเปลี่ยนแปลง
- ก่อนอื่น โปรดสังเกตว่าเนื้อหาทั้งหมดของ
MyHomePage
ได้รับการดึงข้อมูลไปยังวิดเจ็ตใหม่GeneratorPage
เฉพาะส่วนScaffold
ของวิดเจ็ตMyHomePage
เก่าเท่านั้นที่ไม่มีการดึงข้อมูล MyHomePage
ใหม่มีRow
ที่มีบุตร 2 คน วิดเจ็ตแรกคือSafeArea
และวิดเจ็ตที่ 2 คือวิดเจ็ตExpanded
SafeArea
ช่วยให้มั่นใจว่าองค์ประกอบย่อยจะไม่ถูกบดบังโดยรอยบากของฮาร์ดแวร์หรือแถบสถานะ ในแอปนี้ วิดเจ็ตจะตัดขึ้นด้านบนและด้านล่างของNavigationRail
เพื่อป้องกันไม่ให้แถบสถานะของอุปกรณ์เคลื่อนที่บดบังปุ่มการนําทาง- คุณเปลี่ยนบรรทัด
extended: false
ใน NavigationRail เป็นtrue
ได้ ซึ่งจะแสดงป้ายกำกับข้างไอคอน ในขั้นตอนถัดไป คุณจะได้เรียนรู้วิธีดำเนินการนี้โดยอัตโนมัติเมื่อแอปมีพื้นที่แนวนอนเพียงพอ - แถบนําทางมีปลายทาง 2 แห่ง (หน้าแรกและรายการโปรด) พร้อมไอคอนและป้ายกำกับที่เกี่ยวข้อง และยังกำหนด
selectedIndex
ปัจจุบันด้วย ดัชนีที่เลือกเป็น 0 จะเลือกปลายทางแรก ดัชนีที่เลือกเป็น 1 จะเลือกปลายทางที่ 2 และอื่นๆ ขณะนี้ค่านี้ได้รับการฮาร์ดโค้ดเป็น 0 - แถบนําทางยังกําหนดสิ่งที่จะเกิดขึ้นเมื่อผู้ใช้เลือกปลายทางใดปลายทางหนึ่งด้วย
onDestinationSelected
ขณะนี้แอปจะแสดงผลเฉพาะค่าดัชนีที่ขอด้วยprint()
- รายการย่อยที่ 2 ของ
Row
คือวิดเจ็ตExpanded
วิดเจ็ตแบบขยายมีประโยชน์อย่างยิ่งในแถวและคอลัมน์ เนื่องจากช่วยให้คุณแสดงเลย์เอาต์ที่วิดเจ็ตย่อยบางรายการใช้พื้นที่เท่าที่จำเป็น (ในกรณีนี้คือSafeArea
) และวิดเจ็ตอื่นๆ ควรใช้พื้นที่ที่เหลือมากที่สุด (ในกรณีนี้คือExpanded
) วิธีหนึ่งในการมองหาวิดเจ็ตExpanded
คือ "การแย่งพื้นที่" หากต้องการทําความเข้าใจบทบาทของวิดเจ็ตนี้ได้ดียิ่งขึ้น ให้ลองรวมวิดเจ็ตSafeArea
เข้ากับExpanded
อื่น เลย์เอาต์ที่ได้จะมีลักษณะดังนี้
- วิดเจ็ต
Expanded
2 รายการแบ่งพื้นที่แนวนอนทั้งหมดที่มีอยู่ระหว่างกัน แม้ว่าแถบนําทางจะต้องการพื้นที่เพียงเล็กน้อยทางด้านซ้ายเท่านั้น - ภายในวิดเจ็ต
Expanded
จะมีContainer
สี และภายในคอนเทนเนอร์จะมีGeneratorPage
วิดเจ็ตแบบไม่เก็บสถานะกับแบบเก็บสถานะ
ที่ผ่านมา MyAppState
ครอบคลุมความต้องการทั้งหมดของคุณในรัฐ วิดเจ็ตทั้งหมดที่คุณเขียนจนถึงตอนนี้จึงไม่มีสถานะ โดยไม่มีสถานะที่เปลี่ยนแปลงได้ของตนเอง วิดเจ็ตทุกรายการจะเปลี่ยนตัวเองไม่ได้ จะต้องผ่าน MyAppState
แต่เรากำลังจะเปลี่ยนแปลงเรื่องนี้
คุณต้องมีวิธีเก็บค่า selectedIndex
ของแถบนําทาง นอกจากนี้ คุณยังต้องสามารถเปลี่ยนค่านี้ได้จากภายในการเรียกกลับ onDestinationSelected
คุณอาจเพิ่ม selectedIndex
เป็นพร็อพเพอร์ตี้อีกรายการหนึ่งของ MyAppState
และวิธีนี้ได้ผล แต่คุณคงนึกภาพออกว่าสถานะแอปจะเพิ่มขึ้นอย่างรวดเร็วเกินเหตุหากวิดเจ็ตทุกรายการจัดเก็บค่าไว้ในสถานะ
สถานะบางอย่างเกี่ยวข้องกับวิดเจ็ตเดียวเท่านั้น ดังนั้นจึงควรอยู่ในวิดเจ็ตนั้น
ป้อน StatefulWidget
ซึ่งเป็นวิดเจ็ตประเภทที่มี State
ก่อนอื่น ให้แปลง MyHomePage
เป็นวิดเจ็ตที่มีสถานะ
วางเคอร์เซอร์บรรทัดแรกของ MyHomePage
(บรรทัดที่ขึ้นต้นด้วย class MyHomePage...
) แล้วเรียกเมนูปรับโครงสร้างโดยใช้ Ctrl+.
หรือ Cmd+.
จากนั้นเลือกแปลงเป็น StatefulWidget
IDE จะสร้างคลาสใหม่ให้คุณ _MyHomePageState
คลาสนี้ขยาย State
จึงจัดการค่าของตัวเองได้ (สามารถเปลี่ยนตัวเองได้) และโปรดทราบว่าเมธอด build
จากวิดเจ็ตแบบไม่มีสถานะแบบเก่าได้ย้ายไปอยู่ใน _MyHomePageState
แล้ว (แทนที่จะอยู่ในวิดเจ็ต) มีการย้ายวิธีการดังกล่าวโดยถอดความทั้งหมด ไม่มีการแก้ไขใดๆ ในเมธอด build
ตอนนี้มีเพียงเนื้อหาที่แสดงอยู่ที่อื่น
setState
วิดเจ็ตที่มีสถานะใหม่ต้องติดตามตัวแปรเพียง 1 รายการเท่านั้น ซึ่งก็คือ 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
คุณจะกําหนดค่าให้กับselectedIndex
ภายในการเรียกsetState()
แทนที่จะพิมพ์ค่าใหม่ไปยังคอนโซล การเรียกใช้นี้คล้ายกับเมธอดnotifyListeners()
ที่ใช้ก่อนหน้านี้ ซึ่งจะตรวจสอบว่า UI อัปเดตแล้ว
ตอนนี้แถบนําทางจะตอบสนองต่อการโต้ตอบของผู้ใช้ แต่พื้นที่ที่ขยายทางด้านขวาจะยังคงเหมือนเดิม เนื่องจากโค้ดไม่ได้ใช้ selectedIndex
เพื่อกำหนดว่าหน้าจอใดจะแสดง
ใช้ selectedIndex
วางโค้ดต่อไปนี้ที่ด้านบนของเมธอด 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 จะกำหนดหน้าจอให้กับ
page
ตามค่าปัจจุบันในselectedIndex
- เนื่องจากยังไม่มี
FavoritesPage
ให้ใช้Placeholder
ซึ่งเป็นวิดเจ็ตที่มีประโยชน์ซึ่งจะวาดสี่เหลี่ยมผืนผ้าที่มีกากบาทไว้ทุกที่ที่คุณวางไว้ ซึ่งจะระบุว่า UI ส่วนนั้นยังไม่เสร็จ
- การใช้หลักการ "ทำงานให้เสร็จเร็วที่สุด" จะทำให้คำสั่ง 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
ซึ่งจะช่วยให้คุณเปลี่ยนโครงสร้างวิดเจ็ตได้ตามพื้นที่ว่างที่มี
อีกครั้ง ให้ใช้เมนู Refactor ของ Flutter ใน VS Code เพื่อทำการเปลี่ยนแปลงที่จำเป็น แต่ครั้งนี้จะซับซ้อนขึ้นเล็กน้อย โดยทำดังนี้
- วางเคอร์เซอร์ที่
Scaffold
ในเมธอดbuild
ของ_MyHomePageState
- เปิดเมนูปรับโครงสร้างด้วย
Ctrl+.
(Windows/Linux) หรือCmd+.
(Mac) - เลือกตัดด้วย Builder แล้วกด 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
- อย่าลืมเข้าถึงอินสแตนซ์
MyAppState
จากวิดเจ็ตใดก็ได้โดยใช้context.watch<MyAppState>()
- หากต้องการลองใช้วิดเจ็ตใหม่ด้วย
ListTile
ก็มีพร็อพเพอร์ตี้ต่างๆ เช่นtitle
(โดยทั่วไปสําหรับข้อความ)leading
(สําหรับไอคอนหรือรูปโปรไฟล์) และonTap
(สําหรับการโต้ตอบ) อย่างไรก็ตาม คุณสามารถสร้างผลลัพธ์ที่คล้ายกันโดยใช้วิดเจ็ตที่คุณรู้จักอยู่แล้ว - Dart อนุญาตให้ใช้วงเล็บปีกกา
for
ภายในนิพจน์คอลเล็กชัน ตัวอย่างเช่น หากmessages
มีรายการสตริง คุณอาจมีโค้ดดังต่อไปนี้
ในทางกลับกัน หากคุณคุ้นเคยกับโปรแกรมเชิงฟังก์ชันมากกว่า Dart ให้คุณเขียนโค้ดได้เช่นกัน เช่น messages.map((m) => Text(m)).toList()
และแน่นอน คุณสามารถสร้างรายการวิดเจ็ตและเพิ่มลงในรายการนั้นภายในเมธอด build
ได้ทุกเมื่อ
ข้อดีของการเพิ่มหน้ารายการโปรดด้วยตนเองคือคุณจะได้เรียนรู้เพิ่มเติมด้วยการตัดสินใจของคุณเอง ข้อเสียคือคุณอาจพบปัญหาที่แก้ด้วยตนเองไม่ได้ โปรดทราบว่าความผิดพลาดเป็นเรื่องปกติและเป็นหนึ่งในองค์ประกอบที่สำคัญที่สุดของการเรียนรู้ ไม่มีใครคาดหวังให้คุณพัฒนา Flutter ได้อย่างเชี่ยวชาญในชั่วโมงแรก และคุณก็ไม่ควรคาดหวังเช่นนั้นเช่นกัน
ต่อไปนี้เป็นวิธีหนึ่งในการใช้หน้ารายการโปรด วิธีการติดตั้งใช้งาน (หวังว่า) จะช่วยสร้างแรงบันดาลใจให้คุณลองใช้โค้ด ปรับปรุง 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),
),
],
);
}
}
การทำงานของวิดเจ็ตมีดังนี้
- โดยจะรับสถานะปัจจุบันของแอป
- หากรายการโปรดว่างเปล่า ระบบจะแสดงข้อความที่กึ่งกลางว่ายังไม่มีรายการโปรด
- มิฉะนั้น ระบบจะแสดงรายการ (เลื่อนได้)
- รายการจะเริ่มต้นด้วยข้อมูลสรุป (เช่น คุณมีรายการโปรด 5 รายการ*)
- จากนั้นโค้ดจะวนดูรายการโปรดทั้งหมดและสร้างวิดเจ็ต
ListTile
ให้กับรายการโปรดแต่ละรายการ
ขั้นตอนสุดท้ายคือแทนที่วิดเจ็ต Placeholder
ด้วย FavoritesPage
เท่านี้แหละ
คุณดูโค้ดสุดท้ายของแอปนี้ได้ในที่เก็บ Codelab ใน GitHub
9. ขั้นตอนถัดไป
ยินดีด้วย
ดูคุณสิ คุณนำสคาฟเฟลด์ที่ไม่ทำงานซึ่งมีวิดเจ็ต Column
และ Text
2 รายการมาสร้างเป็นแอปเล็กๆ ที่ตอบสนองได้ดีและน่าพอใจ
สิ่งที่เราได้พูดถึง
- ข้อมูลเบื้องต้นเกี่ยวกับวิธีการทำงานของ Flutter
- การสร้างเลย์เอาต์ใน Flutter
- เชื่อมโยงการโต้ตอบของผู้ใช้ (เช่น การกดปุ่ม) กับลักษณะการทํางานของแอป
- จัดระเบียบโค้ด Flutter
- การทำให้แอปปรับเปลี่ยนตามอุปกรณ์ได้
- การสร้างรูปลักษณ์ของแอปให้สอดคล้องกัน
มีอะไรต่อไป
- ลองใช้แอปที่คุณเขียนระหว่างการทดลองนี้เพิ่มเติม
- ดูโค้ดของเวอร์ชันขั้นสูงนี้ของแอปเดียวกันเพื่อดูวิธีเพิ่มรายการที่เคลื่อนไหว ไล่ระดับสี การเฟดเข้าออก และอื่นๆ
- ศึกษาเส้นทางการเรียนรู้โดยไปที่ flutter.dev/learn