เกี่ยวกับ Codelab นี้
1 บทนำ
ภาพเคลื่อนไหวเป็นวิธีที่ยอดเยี่ยมในการปรับปรุงประสบการณ์ของผู้ใช้ในแอป สื่อสารข้อมูลสำคัญกับผู้ใช้ และทำให้แอปดูดียิ่งขึ้นและน่าใช้
ภาพรวมของเฟรมเวิร์กภาพเคลื่อนไหวของ Flutter
Flutter แสดงเอฟเฟกต์ภาพเคลื่อนไหวโดยการสร้างบางส่วนของต้นไม้วิดเจ็ตขึ้นมาใหม่ในแต่ละเฟรม โดยจะมีเอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าและ API อื่นๆ เพื่อช่วยให้คุณสร้างและประกอบภาพเคลื่อนไหวได้ง่ายขึ้น
- ภาพเคลื่อนไหวโดยนัยคือเอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าซึ่งจะแสดงภาพเคลื่อนไหวทั้งหมดโดยอัตโนมัติ เมื่อค่า target ของภาพเคลื่อนไหวเปลี่ยนแปลง ระบบจะแสดงภาพเคลื่อนไหวจากค่าปัจจุบันไปยังค่าเป้าหมาย และแสดงค่าแต่ละค่าในระหว่างนั้นเพื่อให้วิดเจ็ตเคลื่อนไหวอย่างราบรื่น ตัวอย่างภาพเคลื่อนไหวโดยนัย ได้แก่
AnimatedSize
,AnimatedScale
และAnimatedPositioned
- ภาพเคลื่อนไหวที่ชัดเจนก็เป็นเอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าเช่นกัน แต่ต้องใช้ออบเจ็กต์
Animation
จึงจะใช้งานได้ เช่นSizeTransition
,ScaleTransition
หรือPositionedTransition
- Animation คือคลาสที่แสดงภาพเคลื่อนไหวที่ทำงานอยู่หรือหยุดทำงาน และประกอบด้วย value ที่แสดงค่าเป้าหมายที่ภาพเคลื่อนไหวกำลังทำงานอยู่ และ status ที่แสดงค่าปัจจุบันที่ภาพเคลื่อนไหวแสดงบนหน้าจอ ณ เวลาหนึ่งๆ ซึ่งเป็นคลาสย่อยของ
Listenable
และจะแจ้งให้ผู้ฟังทราบเมื่อสถานะมีการเปลี่ยนแปลงขณะที่ภาพเคลื่อนไหวกำลังทำงาน - AnimationController เป็นวิธีสร้างภาพเคลื่อนไหวและควบคุมสถานะ เมธอดต่างๆ เช่น
forward()
,reset()
,stop()
และrepeat()
สามารถใช้เพื่อควบคุมภาพเคลื่อนไหวได้โดยไม่ต้องกำหนดเอฟเฟกต์ภาพเคลื่อนไหวที่แสดง เช่น สเกล ขนาด หรือตำแหน่ง - Tween ใช้เพื่อประมาณค่าระหว่างค่าเริ่มต้นและค่าสิ้นสุด และสามารถแสดงค่าประเภทใดก็ได้ เช่น เลขทศนิยม,
Offset
หรือColor
- เส้นโค้งใช้เพื่อปรับอัตราการเปลี่ยนแปลงของพารามิเตอร์เมื่อเวลาผ่านไป เมื่อภาพเคลื่อนไหวทำงาน โดยทั่วไปจะใช้เส้นโค้งการผ่อนปรนเพื่อทำให้อัตราการเปลี่ยนแปลงเร็วขึ้นหรือช้าลงในช่วงเริ่มต้นหรือตอนท้ายของภาพเคลื่อนไหว เส้นโค้งจะรับค่าอินพุตระหว่าง 0.0 ถึง 1.0 และแสดงผลค่าเอาต์พุตระหว่าง 0.0 ถึง 1.0
สิ่งที่คุณจะสร้าง
ในโค้ดแล็บนี้ คุณจะได้สร้างเกมแบบตอบคำถามแบบหลายตัวเลือกที่มีเอฟเฟกต์และเทคนิคภาพเคลื่อนไหวต่างๆ
คุณจะเห็นวิธีต่อไปนี้
- สร้างวิดเจ็ตที่มีภาพเคลื่อนไหวของขนาดและสี
- สร้างเอฟเฟกต์การพลิกการ์ด 3 มิติ
- ใช้เอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าจากแพ็กเกจภาพเคลื่อนไหว
- เพิ่มการรองรับท่าทางสัมผัสการย้อนกลับที่คาดการณ์ได้ใน Android เวอร์ชันล่าสุด
สิ่งที่คุณจะได้เรียนรู้
ในโค้ดแล็บนี้ คุณจะได้เรียนรู้สิ่งต่อไปนี้
- วิธีใช้เอฟเฟกต์ภาพเคลื่อนไหวโดยนัยเพื่อให้ได้ภาพเคลื่อนไหวที่ดูดีโดยไม่ต้องใช้โค้ดจำนวนมาก
- วิธีใช้เอฟเฟกต์ภาพเคลื่อนไหวที่ชัดเจนเพื่อกำหนดค่าเอฟเฟกต์ของคุณเองโดยใช้วิดเจ็ตภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้า เช่น
AnimatedSwitcher
หรือAnimationController
- วิธีใช้
AnimationController
เพื่อกำหนดวิดเจ็ตของคุณเองซึ่งแสดงผลเป็นเอฟเฟกต์ 3 มิติ - วิธีใช้
animations
package เพื่อแสดงเอฟเฟกต์ภาพเคลื่อนไหวแฟนซีด้วยการตั้งค่าเพียงเล็กน้อย
สิ่งที่คุณต้องมี
- Flutter SDK
- IDE เช่น VSCode หรือ Android Studio / IntelliJ
2 ตั้งค่าสภาพแวดล้อมการพัฒนา Flutter
คุณต้องใช้ซอฟต์แวร์ 2 อย่างในการฝึกนี้ ได้แก่ Flutter SDK และเครื่องมือแก้ไข
คุณเรียกใช้โค้ดแล็บได้โดยใช้อุปกรณ์ต่อไปนี้
- อุปกรณ์ Android (แนะนำสำหรับการใช้การคาดคะเนการกดกลับในขั้นตอนที่ 7) หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาซอฟต์แวร์
- โปรแกรมจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องมีการตั้งค่าใน Android Studio)
- เบราว์เซอร์ (ต้องใช้ Chrome สำหรับการแก้ไขข้อบกพร่อง)
- คอมพิวเตอร์เดสก์ท็อปที่ใช้ Windows, Linux หรือ macOS คุณต้องพัฒนาในแพลตฟอร์มที่คุณวางแผนจะใช้งาน ดังนั้น หากต้องการพัฒนาแอปเดสก์ท็อปของ Windows คุณต้องพัฒนาใน Windows เพื่อเข้าถึงเชนการบิลด์ที่เหมาะสม มีข้อกำหนดเฉพาะสำหรับระบบปฏิบัติการที่อธิบายไว้อย่างละเอียดใน docs.flutter.dev/desktop
ยืนยันการติดตั้ง
หากต้องการยืนยันว่า Flutter SDK ได้รับการกําหนดค่าอย่างถูกต้องและคุณได้ติดตั้งแพลตฟอร์มเป้าหมายอย่างน้อย 1 รายการข้างต้นแล้ว ให้ใช้เครื่องมือ Flutter Doctor โดยทำดังนี้
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.24.2, on macOS 14.6.1 23G93 darwin-arm64, locale en) [✓] Android toolchain - develop for Android devices [✓] Xcode - develop for iOS and macOS [✓] Chrome - develop for the web [✓] Android Studio [✓] IntelliJ IDEA Ultimate Edition [✓] VS Code [✓] Connected device (4 available) [✓] Network resources • No issues found!
3 เรียกใช้แอปเริ่มต้น
ดาวน์โหลดแอปเริ่มต้น
ใช้ git
เพื่อโคลนแอปเริ่มต้นจากที่เก็บ flutter/samples
ใน GitHub
git clone https://github.com/flutter/codelabs.git cd codelabs/animations/step_01/
หรือจะดาวน์โหลดซอร์สโค้ดเป็นไฟล์ ZIP ก็ได้
เรียกใช้แอป
หากต้องการเรียกใช้แอป ให้ใช้คําสั่ง flutter run
และระบุอุปกรณ์เป้าหมาย เช่น android
, ios
หรือ chrome
ดูรายการแพลตฟอร์มทั้งหมดที่รองรับได้ที่หน้าแพลตฟอร์มที่รองรับ
flutter run -d android
นอกจากนี้ คุณยังเรียกใช้และแก้ไขข้อบกพร่องของแอปโดยใช้ IDE ที่ต้องการได้ด้วย ดูข้อมูลเพิ่มเติมในเอกสารประกอบอย่างเป็นทางการของ Flutter
ทัวร์ชมโค้ด
แอปเริ่มต้นเป็นเกมแบบทดสอบแบบหลายตัวเลือกที่มี 2 หน้าจอตามรูปแบบการออกแบบ Model-View-View-Model หรือ MVVM QuestionScreen
(มุมมอง) ใช้คลาส QuizViewModel
(มุมมองโมเดล) เพื่อถามคำถามแบบหลายตัวเลือกจากคลาส QuestionBank
(โมเดล) แก่ผู้ใช้
- home_screen.dart - แสดงหน้าจอที่มีปุ่มเกมใหม่
- main.dart - กําหนดค่า
MaterialApp
ให้ใช้ Material 3 และแสดงหน้าจอหลัก - model.dart - กำหนดคลาสหลักที่ใช้ทั่วทั้งแอป
- question_screen.dart - แสดง UI สำหรับเกมคำถาม
- view_model.dart - จัดเก็บสถานะและตรรกะสำหรับเกมแบบทดสอบที่แสดงโดย
QuestionScreen
แอปยังไม่รองรับเอฟเฟกต์ภาพเคลื่อนไหวใดๆ ยกเว้นการเปลี่ยนมุมมองเริ่มต้นที่แสดงโดยคลาส Navigator
ของ Flutter เมื่อผู้ใช้กดปุ่มเกมใหม่
4 ใช้เอฟเฟกต์ภาพเคลื่อนไหวโดยนัย
ภาพเคลื่อนไหวโดยนัยเป็นตัวเลือกที่ยอดเยี่ยมในหลายสถานการณ์ เนื่องจากไม่ต้องมีการกําหนดค่าพิเศษใดๆ ในส่วนนี้ คุณจะอัปเดตวิดเจ็ต StatusBar
เพื่อให้แสดงตารางสรุปผลแบบเคลื่อนไหว หากต้องการดูเอฟเฟกต์ภาพเคลื่อนไหวโดยนัยทั่วไป ให้เรียกดูเอกสารประกอบของ ImplicitlyAnimatedWidget API
สร้างวิดเจ็ตตารางสรุปสถิติแบบไม่มีภาพเคลื่อนไหว
สร้างไฟล์ใหม่ lib/scoreboard.dart
ที่มีโค้ดต่อไปนี้
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
Icon(
Icons.star,
size: 50,
color: score < i + 1
? Colors.grey.shade400
: Colors.yellow.shade700,
),
],
),
);
}
}
จากนั้นเพิ่มวิดเจ็ต Scoreboard
ในรายการย่อยของวิดเจ็ต StatusBar
โดยแทนที่วิดเจ็ต Text
ที่ก่อนหน้านี้แสดงคะแนนและจำนวนคำถามทั้งหมด เครื่องมือแก้ไขควรเพิ่ม import "scoreboard.dart"
ที่จำเป็นที่ด้านบนของไฟล์โดยอัตโนมัติ
lib/question_screen.dart
class StatusBar extends StatelessWidget {
final QuizViewModel viewModel;
const StatusBar({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Scoreboard( // NEW
score: viewModel.score, // NEW
totalQuestions: viewModel.totalQuestions, // NEW
),
],
),
),
);
}
}
วิดเจ็ตนี้จะแสดงไอคอนดาวสำหรับแต่ละคำถาม เมื่อตอบคำถามถูกต้องแล้ว ดาวอีกดวงจะสว่างขึ้นทันทีโดยไม่มีภาพเคลื่อนไหว ในขั้นตอนต่อไปนี้ คุณจะแจ้งให้ผู้ใช้ทราบว่าคะแนนมีการเปลี่ยนแปลงโดยแสดงผลขนาดและสีของคะแนนเป็นภาพเคลื่อนไหว
ใช้เอฟเฟกต์ภาพเคลื่อนไหวโดยนัย
สร้างวิดเจ็ตใหม่ชื่อ AnimatedStar
ที่ใช้วิดเจ็ต AnimatedScale
เพื่อเปลี่ยนจำนวน scale
จาก 0.5
เป็น 1.0
เมื่อดาวทำงานอยู่ โดยทำดังนี้
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
AnimatedStar(isActive: score > i), // Edit this line.
],
),
);
}
}
class AnimatedStar extends StatelessWidget { // Add from here...
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: Icon(
Icons.star,
size: 50,
color: isActive ? _activatedColor : _deactivatedColor,
),
);
}
} // To here.
ตอนนี้เมื่อผู้ใช้ตอบคำถามอย่างถูกต้อง วิดเจ็ต AnimatedStar
จะอัปเดตขนาดโดยใช้ภาพเคลื่อนไหวโดยนัย color
ของ Icon
จะไม่เคลื่อนไหวที่นี่ มีเพียง scale
เท่านั้นที่เคลื่อนไหว ซึ่งดำเนินการโดยวิดเจ็ต AnimatedScale
ใช้ Tween เพื่อหาค่าประมาณระหว่าง 2 ค่า
โปรดสังเกตว่าสีของวิดเจ็ต AnimatedStar
จะเปลี่ยนทันทีหลังจากช่อง isActive
เปลี่ยนเป็น "จริง"
หากต้องการสร้างเอฟเฟกต์สีแบบเคลื่อนไหว คุณอาจลองใช้วิดเจ็ต AnimatedContainer
(ซึ่งเป็นคลาสย่อยอีกคลาสหนึ่งของ ImplicitlyAnimatedWidget
) เนื่องจากวิดเจ็ตนี้สามารถทำให้แอตทริบิวต์ทั้งหมด รวมถึงสี เคลื่อนไหวได้โดยอัตโนมัติ ขออภัย วิดเจ็ตของเราต้องแสดงไอคอน ไม่ใช่คอนเทนเนอร์
คุณอาจลองใช้ AnimatedIcon
ซึ่งใช้เอฟเฟกต์การเปลี่ยนระหว่างรูปร่างของไอคอน แต่ไม่มีการใช้ไอคอนดาวเริ่มต้นในคลาส AnimatedIcons
แต่เราจะใช้คลาสย่อยอีกคลาสหนึ่งของ ImplicitlyAnimatedWidget
ที่เรียกว่า TweenAnimationBuilder
ซึ่งใช้ Tween
เป็นพารามิเตอร์แทน Tween คือคลาสที่ใช้ค่า 2 ค่า (begin
และ end
) และคำนวณค่าที่อยู่ตรงกลางเพื่อให้ภาพเคลื่อนไหวแสดงค่าเหล่านั้นได้ ในตัวอย่างนี้ เราจะใช้ ColorTween
ซึ่งเป็นไปตามอินเทอร์เฟซ Tween
ที่จําเป็นต่อการสร้างเอฟเฟกต์ภาพเคลื่อนไหว
เลือกวิดเจ็ต Icon
และใช้การดำเนินการด่วน "ตัดกับโปรแกรมสร้าง" ใน IDE แล้วเปลี่ยนชื่อเป็น TweenAnimationBuilder
จากนั้นระบุระยะเวลาและ ColorTween
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: TweenAnimationBuilder( // Add from here...
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) { // To here.
return Icon(Icons.star, size: 50, color: value); // And modify this line.
},
),
);
}
}
จากนั้นโหลดแอปซ้ำขณะทำงานเพื่อดูภาพเคลื่อนไหวใหม่
โปรดทราบว่าค่า end
ของ ColorTween
จะเปลี่ยนแปลงตามค่าของพารามิเตอร์ isActive
เนื่องจาก TweenAnimationBuilder
จะเรียกใช้ภาพเคลื่อนไหวซ้ำทุกครั้งที่ค่า Tween.end
เปลี่ยนแปลง เมื่อเกิดกรณีนี้ขึ้น ภาพเคลื่อนไหวใหม่จะทำงานจากค่าภาพเคลื่อนไหวปัจจุบันไปยังค่าสิ้นสุดใหม่ ซึ่งจะช่วยให้คุณเปลี่ยนสีได้ทุกเมื่อ (แม้ในขณะที่ภาพเคลื่อนไหวกำลังทำงานอยู่) และแสดงเอฟเฟกต์ภาพเคลื่อนไหวที่ราบรื่นด้วยค่ากลางที่ถูกต้อง
ใช้เส้นโค้ง
เอฟเฟกต์ภาพเคลื่อนไหวทั้ง 2 ประเภทนี้ทำงานในอัตราที่คงที่ แต่ภาพเคลื่อนไหวมักจะน่าสนใจและให้ข้อมูลมากกว่าเมื่อเล่นเร็วขึ้นหรือช้าลง
Curve
ใช้ฟังก์ชันการลดลง ซึ่งกําหนดอัตราการเปลี่ยนแปลงของพารามิเตอร์เมื่อเวลาผ่านไป Flutter มาพร้อมกับคอลเล็กชันเส้นโค้งการผ่อนคลายที่สร้างไว้ล่วงหน้าในคลาส Curves
เช่น easeIn
หรือ easeOut
แผนภาพเหล่านี้ (ดูได้ในCurves
หน้าเอกสารประกอบ API) จะให้คําแนะนําเกี่ยวกับวิธีการทำงานของเส้นโค้ง เส้นโค้งจะแปลงค่าอินพุตระหว่าง 0.0 ถึง 1.0 (แสดงบนแกน x) เป็นค่าเอาต์พุตระหว่าง 0.0 ถึง 1.0 (แสดงบนแกน y) แผนภาพเหล่านี้ยังแสดงตัวอย่างลักษณะของเอฟเฟกต์ภาพเคลื่อนไหวต่างๆ เมื่อใช้เส้นโค้งการผ่อนคลายด้วย
สร้างช่องใหม่ใน AnimatedStar ชื่อ _curve
แล้วส่งเป็นพารามิเตอร์ไปยังวิดเจ็ต AnimatedScale
และ TweenAnimationBuilder
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
final Curve _curve = Curves.elasticOut; // NEW
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
curve: _curve, // NEW
duration: _duration,
child: TweenAnimationBuilder(
curve: _curve, // NEW
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) {
return Icon(Icons.star, size: 50, color: value);
},
),
);
}
}
ในตัวอย่างนี้ เส้นโค้ง elasticOut
ให้เอฟเฟกต์สปริงที่เกินจริงซึ่งเริ่มต้นด้วยการเคลื่อนไหวแบบสปริงและสมดุลกันในตอนท้าย
โหลดแอปซ้ำขณะทำงานเพื่อดูว่าเส้นโค้งนี้มีผลกับ AnimatedSize
และ TweenAnimationBuilder
หรือไม่
ใช้เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์เพื่อเปิดใช้ภาพเคลื่อนไหวแบบช้า
หากต้องการแก้ไขข้อบกพร่องของเอฟเฟกต์ภาพเคลื่อนไหว Flutter DevTools มีวิธีทำให้ภาพเคลื่อนไหวทั้งหมดในแอปช้าลงเพื่อให้คุณเห็นภาพเคลื่อนไหวได้ชัดเจนยิ่งขึ้น
หากต้องการเปิด DevTools ให้ตรวจสอบว่าแอปทำงานอยู่ในโหมดแก้ไขข้อบกพร่อง และเปิดเครื่องมือตรวจสอบวิดเจ็ตโดยเลือกในแถบเครื่องมือแก้ไขข้อบกพร่องใน VSCode หรือเลือกปุ่มเปิด Flutter DevTools ในหน้าต่างเครื่องมือแก้ไขข้อบกพร่องใน IntelliJ / Android Studio
เมื่อเครื่องมือตรวจสอบวิดเจ็ตเปิดขึ้น ให้คลิกปุ่มภาพเคลื่อนไหวช้าในแถบเครื่องมือ
5 ใช้เอฟเฟกต์ภาพเคลื่อนไหวที่โจ่งแจ้ง
ภาพเคลื่อนไหวที่ชัดเจนเป็นเอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าเช่นเดียวกับภาพเคลื่อนไหวโดยนัย แต่จะใช้ออบเจ็กต์ Animation
เป็นพารามิเตอร์แทนที่จะใช้ค่าเป้าหมาย ซึ่งทำให้มีประโยชน์ในสถานการณ์ที่ภาพเคลื่อนไหวได้รับการกำหนดโดยการเปลี่ยนเส้นทางการนำทาง AnimatedSwitcher
หรือ AnimationController
อยู่แล้ว เป็นต้น
ใช้เอฟเฟกต์ภาพเคลื่อนไหวที่ชัดเจน
หากต้องการเริ่มต้นใช้งานเอฟเฟกต์ภาพเคลื่อนไหวที่ชัดเจน ให้ใส่วิดเจ็ต Card
ใน AnimatedSwitcher
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({required this.question, super.key});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
), // NEW
);
}
}
AnimatedSwitcher
ใช้เอฟเฟกต์การเฟดโดยค่าเริ่มต้น แต่คุณลบล้างค่านี้ได้โดยใช้พารามิเตอร์ transitionBuilder
ตัวสร้างทรานซิชันจะระบุวิดเจ็ตย่อยที่ส่งไปยัง AnimatedSwitcher
และออบเจ็กต์ Animation
นี่เป็นโอกาสที่ดีในการใช้ภาพเคลื่อนไหวที่ชัดเจน
สําหรับโค้ดแล็บนี้ ภาพเคลื่อนไหวที่ชัดเจนรายการแรกที่เราจะใช้คือ SlideTransition
ซึ่งใช้ Animation<Offset>
ที่กําหนดระยะเริ่มต้นและระยะสิ้นสุดที่วิดเจ็ตขาเข้าและขาออกจะเคลื่อนไหว
Tween มีฟังก์ชันตัวช่วย animate()
ซึ่งจะแปลง Animation
รูปแบบหนึ่งเป็น Animation
รูปแบบอื่นโดยใช้ Tween ซึ่งหมายความว่า Tween
สามารถใช้เพื่อแปลง Animation
ที่ AnimatedSwitcher
ระบุเป็น Animation
เพื่อส่งไปยังวิดเจ็ต SlideTransition
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({required this.question, super.key});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
transitionBuilder: (child, animation) { // Add from here...
final curveAnimation = CurveTween(
curve: Curves.easeInCubic,
).animate(animation);
final offsetAnimation = Tween<Offset>(
begin: Offset(-0.1, 0.0),
end: Offset.zero,
).animate(curveAnimation);
return SlideTransition(position: offsetAnimation, child: child);
}, // To here.
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
โปรดทราบว่าการดําเนินการนี้ใช้ Tween.animate
เพื่อใช้ Curve
กับ Animation
จากนั้นแปลงจาก Tween
ที่มีช่วง 0.0 ถึง 1.0 เป็น Tween
ที่เปลี่ยนจาก -0.1 เป็น 0.0 ในแกน x
หรือคลาส Animation จะมีฟังก์ชัน drive()
ที่รับ Tween
(หรือ Animatable
) ใดก็ได้และแปลงเป็น Animation
ใหม่ ซึ่งช่วยให้ "เชน" Tween ได้ ทำให้โค้ดที่ได้กระชับมากขึ้น
lib/question_screen.dart
transitionBuilder: (child, animation) {
var offsetAnimation = animation
.drive(CurveTween(curve: Curves.easeInCubic))
.drive(Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero));
return SlideTransition(position: offsetAnimation, child: child);
},
ข้อดีอีกอย่างหนึ่งของการใช้ภาพเคลื่อนไหวที่ชัดเจนคือสามารถประกอบเข้าด้วยกันได้ เพิ่มภาพเคลื่อนไหวที่ชัดเจนอีกรายการ FadeTransition
ที่ใช้ภาพเคลื่อนไหวโค้งแบบเดียวกันโดยตัดวิดเจ็ต SlideTransition
lib/question_screen.dart
return AnimatedSwitcher(
transitionBuilder: (child, animation) {
final curveAnimation = CurveTween(
curve: Curves.easeInCubic,
).animate(animation);
final offsetAnimation = Tween<Offset>(
begin: Offset(-0.1, 0.0),
end: Offset.zero,
).animate(curveAnimation);
final fadeInAnimation = curveAnimation; // NEW
return FadeTransition( // NEW
opacity: fadeInAnimation, // NEW
child: SlideTransition(position: offsetAnimation, child: child), // NEW
); // NEW
},
ปรับแต่ง layoutBuilder
คุณอาจสังเกตเห็นปัญหาเล็กๆ น้อยๆ เกี่ยวกับ AnimationSwitcher
เมื่อ QuestionCard
เปลี่ยนไปใช้คำถามใหม่ ระบบจะวางคำถามนั้นไว้ตรงกลางพื้นที่ว่างขณะที่ภาพเคลื่อนไหวทำงาน แต่เมื่อภาพเคลื่อนไหวหยุดลง วิดเจ็ตจะย้ายไปอยู่ที่ด้านบนของหน้าจอ ซึ่งทำให้ภาพเคลื่อนไหวกระตุกเนื่องจากตำแหน่งสุดท้ายของการ์ดคำถามไม่ตรงกับตำแหน่งขณะที่ภาพเคลื่อนไหวทำงานอยู่
ในการแก้ไขปัญหานี้ AnimatedSwitcher
ยังมีพารามิเตอร์ layoutBuilder
อีกด้วย ซึ่งสามารถใช้เพื่อกําหนดเลย์เอาต์ ใช้ฟังก์ชันนี้เพื่อกําหนดค่าเครื่องมือสร้างเลย์เอาต์ให้จัดการ์ดให้อยู่ด้านบนของหน้าจอ
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
layoutBuilder: (currentChild, previousChildren) {
return Stack(
alignment: Alignment.topCenter,
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
);
},
โค้ดนี้เป็น defaultLayoutBuilder เวอร์ชันแก้ไขจากคลาส AnimatedSwitcher
แต่ใช้ Alignment.topCenter
แทน Alignment.center
สรุป
- ภาพเคลื่อนไหวที่ชัดเจนคือเอฟเฟกต์ภาพเคลื่อนไหวที่ใช้ออบเจ็กต์
Animation
(ตรงข้ามกับImplicitlyAnimatedWidgets
ซึ่งใช้value
และduration
เป้าหมาย) - คลาส
Animation
แสดงภาพเคลื่อนไหวที่ทำงานอยู่ แต่ไม่กำหนดเอฟเฟกต์ที่เฉพาะเจาะจง - ใช้
Tween().animate
หรือAnimation.drive()
เพื่อใช้Tweens
และCurves
(โดยใช้CurveTween
) กับภาพเคลื่อนไหว - ใช้พารามิเตอร์
layoutBuilder
ของAnimatedSwitcher
เพื่อปรับวิธีจัดวางองค์ประกอบย่อย
6 ควบคุมสถานะของภาพเคลื่อนไหว
ที่ผ่านมาเฟรมเวิร์กจะเรียกใช้ภาพเคลื่อนไหวทุกรายการโดยอัตโนมัติ ภาพเคลื่อนไหวโดยนัยจะทำงานโดยอัตโนมัติ ส่วนเอฟเฟกต์ภาพเคลื่อนไหวโดยชัดแจ้งต้องใช้ Animation
จึงจะทำงานได้อย่างถูกต้อง ในส่วนนี้ คุณจะได้เรียนรู้วิธีสร้างออบเจ็กต์ Animation
ของคุณเองโดยใช้ AnimationController
และใช้ TweenSequence
เพื่อรวม Tween
เข้าด้วยกัน
เรียกใช้ภาพเคลื่อนไหวโดยใช้ AnimationController
หากต้องการสร้างภาพเคลื่อนไหวโดยใช้ AnimationController คุณจะต้องทําตามขั้นตอนต่อไปนี้
- สร้าง
StatefulWidget
- ใช้
SingleTickerProviderStateMixin
mixin ในคลาสState
เพื่อระบุTicker
ให้กับAnimationController
- เริ่มต้น
AnimationController
ในเมธอดวงจรชีวิตของinitState
โดยระบุออบเจ็กต์State
ปัจจุบันไปยังพารามิเตอร์vsync
(TickerProvider
) - ตรวจสอบว่าวิดเจ็ตจะสร้างใหม่ทุกครั้งที่
AnimationController
แจ้งเตือนผู้ฟัง โดยใช้AnimatedBuilder
หรือเรียกlisten()
และsetState
ด้วยตนเอง
สร้างไฟล์ใหม่ flip_effect.dart
แล้วคัดลอกและวางโค้ดต่อไปนี้
lib/flip_effect.dart
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.duration,
);
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
}
@override
void didUpdateWidget(covariant CardFlipEffect oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.child.key != oldWidget.child.key) {
_handleChildChanged(widget.child, oldWidget.child);
}
}
void _handleChildChanged(Widget newChild, Widget previousChild) {
_previousChild = previousChild;
_animationController.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationController.value * math.pi),
child: _animationController.isAnimating
? _animationController.value < 0.5
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
}
คลาสนี้จะตั้งค่า AnimationController
และเรียกใช้ภาพเคลื่อนไหวอีกครั้งทุกครั้งที่เฟรมเวิร์กเรียก didUpdateWidget
เพื่อแจ้งให้ทราบว่ามีการเปลี่ยนแปลงการกำหนดค่าวิดเจ็ตและอาจมีวิดเจ็ตย่อยใหม่
AnimatedBuilder
ช่วยให้มั่นใจได้ว่าระบบจะสร้างต้นไม้วิดเจ็ตขึ้นใหม่ทุกครั้งที่ AnimationController
แจ้งเตือนผู้ฟัง และระบบจะใช้วิดเจ็ต Transform
เพื่อใช้เอฟเฟกต์การหมุน 3 มิติเพื่อจำลองการพลิกการ์ด
หากต้องการใช้วิดเจ็ตนี้ ให้ใส่การ์ดคำตอบแต่ละใบไว้ในวิดเจ็ต CardFlipEffect
ตรวจสอบว่าได้ระบุ key
ให้กับวิดเจ็ต Card
แล้ว โดยทำดังนี้
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card.filled( // NEW
key: ValueKey(answers[index]), // NEW
color: color,
elevation: 2,
margin: EdgeInsets.all(8),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTapped(index),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(
answers.length > index ? answers[index] : '',
style: Theme.of(context).textTheme.titleMedium,
overflow: TextOverflow.clip,
),
),
),
),
), // NEW
);
}),
);
}
ตอนนี้ให้โหลดแอปซ้ำแบบ Hot Reload เพื่อดูการ์ดคำตอบพลิกกลับโดยใช้วิดเจ็ต CardFlipEffect
คุณอาจสังเกตเห็นว่าคลาสนี้มีลักษณะคล้ายกับเอฟเฟกต์ภาพเคลื่อนไหวที่อาจไม่เหมาะสม อันที่จริงแล้ว เราขอแนะนำให้ขยายคลาส AnimatedWidget
โดยตรงเพื่อใช้เวอร์ชันของคุณเอง แต่เนื่องจากคลาสนี้จําเป็นต้องจัดเก็บวิดเจ็ตก่อนหน้าใน State
จึงต้องใช้ StatefulWidget
ดูข้อมูลเพิ่มเติมเกี่ยวกับการสร้างเอฟเฟกต์ภาพเคลื่อนไหวที่ชัดเจนของคุณเองได้ในเอกสารประกอบ API สําหรับ AnimatedWidget
เพิ่มการหน่วงเวลาโดยใช้ TweenSequence
ในส่วนนี้ คุณจะต้องเพิ่มการหน่วงเวลาให้กับวิดเจ็ต CardFlipEffect
เพื่อให้การ์ดแต่ละใบพลิกทีละใบ เริ่มต้นใช้งานโดยเพิ่มช่องใหม่ชื่อ delayAmount
lib/flip_effect.dart
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
final double delayAmount; // NEW
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
required this.delayAmount, // NEW
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
จากนั้นเพิ่ม delayAmount
ลงในเมธอดการสร้าง AnswerCards
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect(
delayAmount: index.toDouble() / 2, // NEW
duration: const Duration(milliseconds: 300),
child: Card.filled(
key: ValueKey(answers[index]),
จากนั้นใน _CardFlipEffectState
ให้สร้าง Animation
ใหม่ที่ใช้การเลื่อนเวลาโดยใช้ TweenSequence
โปรดทราบว่าการดำเนินการนี้ไม่ได้ใช้ยูทิลิตีใดๆ จากไลบรารี dart:async
เช่น Future.delayed
เนื่องจากความล่าช้าเป็นส่วนหนึ่งของภาพเคลื่อนไหวและไม่ใช่สิ่งที่วิดเจ็ตควบคุมอย่างชัดเจนเมื่อใช้ AnimationController
วิธีนี้ช่วยให้แก้ไขข้อบกพร่องของเอฟเฟกต์ภาพเคลื่อนไหวได้ง่ายขึ้นเมื่อเปิดใช้ภาพเคลื่อนไหวช้าในเครื่องมือสำหรับนักพัฒนาเว็บ เนื่องจากใช้ TickerProvider
เดียวกัน
หากต้องการใช้ TweenSequence
ให้สร้าง TweenSequenceItem
2 รายการ โดยรายการหนึ่งมี ConstantTween
ที่ทำให้ภาพเคลื่อนไหวอยู่ที่ 0 เป็นเวลาตามระยะเวลาสัมพัทธ์ และ Tween
ปกติที่วิ่งจาก 0.0
ถึง 1.0
lib/flip_effect.dart
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
late final Animation<double> _animationWithDelay; // NEW
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.duration * (widget.delayAmount + 1),
);
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
_animationWithDelay = TweenSequence<double>([ // Add from here...
if (widget.delayAmount > 0)
TweenSequenceItem(
tween: ConstantTween<double>(0.0),
weight: widget.delayAmount,
),
TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 1.0),
]).animate(_animationController); // To here.
}
สุดท้าย ให้แทนที่ภาพเคลื่อนไหวของ AnimationController
ด้วยภาพเคลื่อนไหวแบบเลื่อนเวลาใหม่ในเมธอด build
lib/flip_effect.dart
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationWithDelay, // Modify this line
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationWithDelay.value * math.pi), // And this line
child: _animationController.isAnimating
? _animationWithDelay.value < 0.5 // And this one.
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
ตอนนี้ให้โหลดแอปซ้ำแบบ Hot Reload แล้วดูการ์ดพลิกทีละใบ หากต้องการลองท้าทายตัวเอง ให้ลองเปลี่ยนมุมมองของเอฟเฟกต์ 3 มิติที่วิดเจ็ต Transform
มีให้
7 ใช้ทรานซิชันการนำทางที่กำหนดเอง
จนถึงตอนนี้ เราได้ดูวิธีปรับแต่งเอฟเฟกต์ในหน้าจอเดียวแล้ว แต่อีกวิธีในการใช้ภาพเคลื่อนไหวคือการใช้ภาพเคลื่อนไหวเพื่อเปลี่ยนระหว่างหน้าจอ ในส่วนนี้ คุณจะได้เรียนรู้วิธีใช้เอฟเฟกต์ภาพเคลื่อนไหวกับการเปลี่ยนหน้าจอโดยใช้เอฟเฟกต์ภาพเคลื่อนไหวในตัวและเอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าซึ่งดูน่าสนใจจากแพ็กเกจ animations อย่างเป็นทางการใน pub.dev
สร้างภาพเคลื่อนไหวการเปลี่ยนการนำทาง
คลาส PageRouteBuilder
คือ Route
ที่ช่วยให้คุณปรับแต่งภาพเคลื่อนไหวการเปลี่ยนได้ ซึ่งช่วยให้คุณลบล้างการเรียกกลับ transitionBuilder
ของ Navigator ได้ ซึ่งจะสร้างออบเจ็กต์ Animation 2 รายการ ซึ่งแสดงภาพเคลื่อนไหวขาเข้าและขาออกที่ Navigator เรียกใช้
หากต้องการปรับแต่งภาพเคลื่อนไหวการเปลี่ยน ให้แทนที่ MaterialPageRoute
ด้วย PageRouteBuilder
และหากต้องการปรับแต่งภาพเคลื่อนไหวการเปลี่ยนเมื่อผู้ใช้ไปยัง HomeScreen
จาก QuestionScreen
ใช้ FadeTransition
(วิดเจ็ตที่เคลื่อนไหวอย่างชัดแจ้ง) เพื่อทำให้หน้าจอใหม่ค่อยๆ ปรากฏขึ้นบนหน้าจอก่อนหน้า
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder( // Add from here...
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
), // To here.
);
},
child: Text('New Game'),
),
แพ็กเกจภาพเคลื่อนไหวมีเอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าอย่าง FadeThroughTransition
นําเข้าแพ็กเกจภาพเคลื่อนไหวและแทนที่ FadeTransition
ด้วยวิดเจ็ต FadeThroughTransition
lib/home_screen.dart
import 'package;animations/animations.dart';
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition( // Add from here...
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
); // To here.
},
),
);
},
child: Text('New Game'),
),
ปรับแต่งภาพเคลื่อนไหวย้อนกลับแบบคาดเดา
การย้อนกลับแบบคาดการณ์เป็นฟีเจอร์ใหม่ของ Android ที่ช่วยให้ผู้ใช้ดูเส้นทางหรือแอปปัจจุบันเพื่อดูสิ่งที่อยู่เบื้องหลังได้ก่อนที่จะไปยังส่วนถัดไป ภาพเคลื่อนไหวของตัวอย่างจะอิงตามตําแหน่งของนิ้วของผู้ใช้ขณะที่ลากกลับบนหน้าจอ
Flutter รองรับการกดย้อนกลับแบบคาดการณ์ของระบบโดยเปิดใช้ฟีเจอร์นี้ที่ระดับระบบเมื่อ Flutter ไม่มีเส้นทางที่จะแสดงในกองการนำทาง หรือกล่าวคือ เมื่อการกดย้อนกลับจะออกจากแอป ระบบจะจัดการภาพเคลื่อนไหวนี้ ไม่ใช่ Flutter
Flutter ยังรองรับการกดย้อนกลับแบบคาดการณ์เมื่อไปยังส่วนต่างๆ ภายในแอป Flutter ด้วย PageTransitionsBuilder
พิเศษที่เรียกว่า PredictiveBackPageTransitionsBuilder
จะคอยฟังท่าทางสัมผัสการกดย้อนกลับแบบคาดการณ์ของระบบ และขับเคลื่อนการเปลี่ยนหน้าเว็บตามความคืบหน้าของท่าทางสัมผัส
การกดย้อนกลับแบบคาดการณ์ใช้ได้ใน Android U ขึ้นไปเท่านั้น แต่ Flutter จะเปลี่ยนไปใช้ลักษณะการทำงานของท่าทางสัมผัสย้อนกลับเดิมและ ZoomPageTransitionBuilder ดูข้อมูลเพิ่มเติมได้ที่บล็อกโพสต์ของเรา รวมถึงส่วนเกี่ยวกับวิธีตั้งค่าในแอปของคุณเอง
ในการกำหนดค่า ThemeData สําหรับแอป ให้กําหนดค่า PageTransitionsTheme
ให้ใช้ PredictiveBack
ใน Android และเอฟเฟกต์การเปลี่ยนผ่านแบบค่อยๆ จางจากแพ็กเกจภาพเคลื่อนไหวในแพลตฟอร์มอื่นๆ ดังนี้
lib/main.dart
import 'package:animations/animations.dart'; // NEW
import 'package:flutter/material.dart';
import 'home_screen.dart';
void main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), // NEW
TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.macOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.windows: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.linux: FadeThroughPageTransitionsBuilder(), // NEW
},
),
),
home: HomeScreen(),
);
}
}
ตอนนี้คุณเปลี่ยนการโทรกลับ Navigator.push()
เป็น MaterialPageRoute
ได้แล้ว
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
MaterialPageRoute( // Add from here...
builder: (context) {
return const QuestionScreen();
},
), // To here.
);
},
child: Text('New Game'),
),
ใช้ FadeThroughTransition เพื่อเปลี่ยนคำถามปัจจุบัน
วิดเจ็ต AnimatedSwitcher
มี Animation
เพียงรายการเดียวในการเรียกกลับของเครื่องมือสร้าง แพ็กเกจ animations
มี PageTransitionSwitcher
เพื่อแก้ไขปัญหานี้
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({required this.question, super.key});
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher( // Add from here...
layoutBuilder: (entries) {
return Stack(alignment: Alignment.topCenter, children: entries);
},
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
}, // To here.
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
ใช้ OpenContainer
วิดเจ็ต OpenContainer จากแพ็กเกจ animations
มีเอฟเฟกต์ภาพเคลื่อนไหวการเปลี่ยนรูปแบบคอนเทนเนอร์ที่ขยายออกเพื่อสร้างการเชื่อมต่อที่มองเห็นได้ระหว่างวิดเจ็ต 2 รายการ
วิดเจ็ตที่ closedBuilder
แสดงผลในตอนแรกจะขยายเป็นวิดเจ็ตที่ openBuilder
แสดงผลเมื่อมีการแตะคอนเทนเนอร์หรือเรียกใช้การเรียกกลับ openContainer
หากต้องการเชื่อมต่อการเรียกคืน openContainer
กับโมเดลมุมมอง ให้เพิ่มพาสใหม่ผ่าน viewModel
ไปยังวิดเจ็ต QuestionCard
และจัดเก็บการเรียกคืนที่จะใช้แสดงหน้าจอ "เกมจบแล้ว" ดังนี้
lib/question_screen.dart
class QuestionScreen extends StatefulWidget {
const QuestionScreen({super.key});
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
late final QuizViewModel viewModel = QuizViewModel(
onGameOver: _handleGameOver,
);
VoidCallback? _showGameOverScreen; // NEW
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: viewModel,
builder: (context, child) {
return Scaffold(
appBar: AppBar(
actions: [
TextButton(
onPressed:
viewModel.hasNextQuestion && viewModel.didAnswerQuestion
? () {
viewModel.getNextQuestion();
}
: null,
child: const Text('Next'),
),
],
),
body: Center(
child: Column(
children: [
QuestionCard( // NEW
onChangeOpenContainer: _handleChangeOpenContainer, // NEW
question: viewModel.currentQuestion?.question, // NEW
viewModel: viewModel, // NEW
), // NEW
Spacer(),
AnswerCards(
onTapped: (index) {
viewModel.checkAnswer(index);
},
answers: viewModel.currentQuestion?.possibleAnswers ?? [],
correctAnswer: viewModel.didAnswerQuestion
? viewModel.currentQuestion?.correctAnswer
: null,
),
StatusBar(viewModel: viewModel),
],
),
),
);
},
);
}
void _handleChangeOpenContainer(VoidCallback openContainer) { // NEW
_showGameOverScreen = openContainer; // NEW
} // NEW
void _handleGameOver() { // NEW
if (_showGameOverScreen != null) { // NEW
_showGameOverScreen!(); // NEW
} // NEW
} // NEW
}
เพิ่มวิดเจ็ตใหม่ GameOverScreen
lib/question_screen.dart
class GameOverScreen extends StatelessWidget {
final QuizViewModel viewModel;
const GameOverScreen({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(automaticallyImplyLeading: false),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Scoreboard(
score: viewModel.score,
totalQuestions: viewModel.totalQuestions,
),
Text('You Win!', style: Theme.of(context).textTheme.displayLarge),
Text(
'Score: ${viewModel.score} / ${viewModel.totalQuestions}',
style: Theme.of(context).textTheme.displaySmall,
),
ElevatedButton(
child: Text('OK'),
onPressed: () {
Navigator.popUntil(context, (route) => route.isFirst);
},
),
],
),
),
);
}
}
ในวิดเจ็ต QuestionCard
ให้แทนที่ Card
ด้วยวิดเจ็ต OpenContainer
จากแพ็กเกจ animations
โดยเพิ่ม 2 ช่องใหม่สําหรับ viewModel
และเปิดการเรียกกลับคอนเทนเนอร์ ดังนี้
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.onChangeOpenContainer,
required this.question,
required this.viewModel,
super.key,
});
final ValueChanged<VoidCallback> onChangeOpenContainer;
final QuizViewModel viewModel;
static const _backgroundColor = Color(0xfff2f3fa);
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: OpenContainer( // NEW
key: ValueKey(question), // NEW
tappable: false, // NEW
closedColor: _backgroundColor, // NEW
closedShape: const RoundedRectangleBorder( // NEW
borderRadius: BorderRadius.all(Radius.circular(12.0)), // NEW
), // NEW
closedElevation: 4, // NEW
closedBuilder: (context, openContainer) { // NEW
onChangeOpenContainer(openContainer); // NEW
return ColoredBox( // NEW
color: _backgroundColor, // NEW
child: Padding( // NEW
padding: const EdgeInsets.all(16.0), // NEW
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
);
},
openBuilder: (context, closeContainer) { // NEW
return GameOverScreen(viewModel: viewModel); // NEW
}, // NEW
),
);
}
}
8 ขอแสดงความยินดี
ขอแสดงความยินดี คุณได้เพิ่มเอฟเฟกต์ภาพเคลื่อนไหวลงในแอป Flutter และเรียนรู้เกี่ยวกับคอมโพเนนต์หลักของระบบภาพเคลื่อนไหวของ Flutter เรียบร้อยแล้ว โดยเฉพาะอย่างยิ่ง คุณได้เรียนรู้สิ่งต่อไปนี้
- วิธีใช้
ImplicitlyAnimatedWidget
- วิธีใช้
ExplicitlyAnimatedWidget
- วิธีใช้
Curves
และTweens
กับภาพเคลื่อนไหว - วิธีใช้วิดเจ็ตทรานซิชันที่สร้างไว้ล่วงหน้า เช่น
AnimatedSwitcher
หรือPageRouteBuilder
- วิธีใช้เอฟเฟกต์ภาพเคลื่อนไหวที่สร้างไว้ล่วงหน้าอย่างสวยงามจากแพ็กเกจ
animations
เช่นFadeThroughTransition
และOpenContainer
- วิธีปรับแต่งภาพเคลื่อนไหวการเปลี่ยนเริ่มต้น รวมถึงการเพิ่มการรองรับการย้อนกลับแบบคาดเดาใน Android
ขั้นตอนถัดไปคือ
ลองดู Codelab ต่อไปนี้
- การสร้างเลย์เอาต์แอปที่ปรับเปลี่ยนตามพื้นที่โฆษณาแบบเคลื่อนไหวด้วย Material 3
- การสร้างทรานซิชันที่สวยงามด้วย Material Motion สำหรับ Flutter
- เปลี่ยนแอป Flutter ของคุณจากน่าเบื่อให้สวยงาม
หรือดาวน์โหลดแอปตัวอย่างภาพเคลื่อนไหวซึ่งแสดงเทคนิคภาพเคลื่อนไหวต่างๆ
อ่านเพิ่มเติม
ดูแหล่งข้อมูลภาพเคลื่อนไหวเพิ่มเติมได้ใน flutter.dev
- ข้อมูลเบื้องต้นเกี่ยวกับภาพเคลื่อนไหว
- บทแนะนำภาพเคลื่อนไหว (บทแนะนำ)
- ภาพเคลื่อนไหวแบบไม่เจาะจง (บทแนะนำ)
- ทำให้พร็อพเพอร์ตี้ของคอนเทนเนอร์เคลื่อนไหว (ตำรา)
- ทำให้วิดเจ็ตปรากฏขึ้นและจางหายไป (ตำราอาหาร)
- ภาพเคลื่อนไหวของรูปภาพหลัก
- ทำให้การเปลี่ยนเส้นทางหน้าเว็บเคลื่อนไหว (ตำรา)
- สร้างภาพเคลื่อนไหวของวิดเจ็ตโดยใช้การจำลองฟิสิกส์ (ตำรา)
- ภาพเคลื่อนไหวแบบสลับ
- วิดเจ็ตภาพเคลื่อนไหวและการเคลื่อนไหว (แคตตาล็อกวิดเจ็ต)
หรืออ่านบทความเหล่านี้ใน Medium
- เจาะลึกภาพเคลื่อนไหว
- ภาพเคลื่อนไหวโดยนัยที่กําหนดเองใน Flutter
- การจัดการภาพเคลื่อนไหวด้วย Flutter และ Flux / Redux
- วิธีเลือกวิดเจ็ตภาพเคลื่อนไหว Flutter ที่เหมาะกับคุณ
- ภาพเคลื่อนไหวตามทิศทางที่มีภาพเคลื่อนไหวที่ชัดเจนในตัว
- ข้อมูลเบื้องต้นเกี่ยวกับภาพเคลื่อนไหวของ Flutter ที่มีภาพเคลื่อนไหวโดยนัย
- ฉันควรใช้ AnimatedBuilder หรือ AnimatedWidget เมื่อใด