แอพเคลื่อนไหวด้วย Flutter

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างย่อ ↬ Flutter ให้การสนับสนุนแอนิเมชั่นที่ยอดเยี่ยมสำหรับแอปข้ามแพลตฟอร์ม บทความนี้สำรวจรูปแบบใหม่ที่แปลกใหม่และวิธีที่ง่ายกว่าในการเพิ่มแอนิเมชั่นไปยังแอป วิดเจ็ต "แอนิเมชั่นและการเคลื่อนไหว" ใหม่เหล่านี้คืออะไร และเราจะใช้วิดเจ็ตเหล่านี้เพื่อเพิ่มแอนิเมชั่นที่เรียบง่ายและซับซ้อนได้อย่างไร

แอพสำหรับแพลตฟอร์มใด ๆ ได้รับการยกย่องเมื่อพวกเขาใช้งานง่าย ดูดี และให้ข้อเสนอแนะที่น่าพึงพอใจต่อการโต้ตอบของผู้ใช้ แอนิเมชั่นเป็นวิธีหนึ่งในการทำเช่นนั้น

Flutter ซึ่งเป็นเฟรมเวิร์กข้ามแพลตฟอร์มได้เติบโตเต็มที่ในช่วงสองปีที่ผ่านมาเพื่อรวมการสนับสนุนเว็บและเดสก์ท็อป ได้รับชื่อเสียงว่าแอพที่พัฒนาขึ้นนั้นมีความราบรื่นและดูดี ด้วยการสนับสนุนแอนิเมชั่นที่หลากหลาย วิธีการเขียน UI อย่างเปิดเผย “Hot Reload” และคุณสมบัติอื่นๆ ตอนนี้เป็นเฟรมเวิร์กข้ามแพลตฟอร์มที่สมบูรณ์แล้ว

หากคุณกำลังเริ่มต้นใช้งาน Flutter และต้องการเรียนรู้วิธีที่แปลกใหม่ในการเพิ่มแอนิเมชั่น แสดงว่าคุณมาถูกที่แล้ว: เราจะสำรวจขอบเขตของแอนิเมชั่นและวิดเจ็ตการเคลื่อนไหว ซึ่งเป็นวิธีการโดยปริยายในการเพิ่มแอนิเมชั่น

Flutter ขึ้นอยู่กับแนวคิดของวิดเจ็ต องค์ประกอบภาพแต่ละอย่างของแอปคือวิดเจ็ต — ให้มองว่าเป็นมุมมองใน Android Flutter ให้การสนับสนุนแอนิเมชั่นโดยใช้คลาสแอนิเมชั่น ออบเจ็กต์ “AnimationController” สำหรับการจัดการ และ “Tween” เพื่อสอดแทรกช่วงของข้อมูล องค์ประกอบทั้งสามนี้ทำงานร่วมกันเพื่อให้แอนิเมชั่นราบรื่น เนื่องจากสิ่งนี้ต้องการการสร้างและการจัดการแอนิเมชั่นด้วยตนเอง จึงเรียกว่าเป็นวิธีการสร้างแอนิเมชั่นที่ชัดเจน

ตอนนี้ ให้ฉันแนะนำคุณเกี่ยวกับวิดเจ็ตแอนิเมชั่นและการเคลื่อนไหว Flutter มีวิดเจ็ตมากมายที่รองรับแอนิเมชั่น ไม่จำเป็นต้องสร้างวัตถุแอนิเมชั่นหรือตัวควบคุมใดๆ เนื่องจากวิดเจ็ตประเภทนี้จัดการแอนิเมชั่นทั้งหมด เพียงเลือกวิดเจ็ตที่เหมาะสมสำหรับแอนิเมชั่นที่ต้องการ และส่งผ่านค่าคุณสมบัติของวิดเจ็ตเพื่อทำให้เคลื่อนไหว เทคนิคนี้เป็นวิธีการสร้างแอนิเมชั่นโดยปริยาย

ลำดับชั้นของแอนิเมชั่นใน Flutter (ตัวอย่างขนาดใหญ่)

แผนภูมิด้านบนแสดงลำดับชั้นของแอนิเมชันคร่าวๆ ใน Flutter วิธีรองรับแอนิเมชันทั้งแบบชัดแจ้งและโดยปริยาย

วิดเจ็ตภาพเคลื่อนไหวบางส่วนที่กล่าวถึงในบทความนี้ ได้แก่:

  • AnimatedOpacity
  • AnimatedCrossFade
  • AnimatedAlign
  • AnimatedPadding
  • AnimatedSize
  • AnimatedPositioned ตำแหน่ง

Flutter ไม่เพียงแต่จัดเตรียมวิดเจ็ตแอนิเมชั่นที่กำหนดไว้ล่วงหน้าเท่านั้น แต่ยังมีวิดเจ็ตทั่วไปที่เรียกว่า AnimatedWidget ซึ่งสามารถใช้เพื่อสร้างวิดเจ็ตแอนิเมชั่นโดยปริยายแบบกำหนดเองได้ ดังที่เห็นได้ชัดจากชื่อ วิดเจ็ตเหล่านี้อยู่ในหมวดหมู่วิดเจ็ตแบบเคลื่อนไหวและแบบเคลื่อนไหว ดังนั้นพวกมันจึงมีคุณสมบัติทั่วไปบางอย่างที่ช่วยให้เราสร้างแอนิเมชั่นได้ราบรื่นยิ่งขึ้นและดูดีขึ้นมาก

ให้ฉันอธิบายคุณสมบัติทั่วไปเหล่านี้ในตอนนี้ เนื่องจากจะใช้ในตัวอย่างทั้งหมดในภายหลัง

  • duration
    ระยะเวลาที่จะทำให้พารามิเตอร์เคลื่อนไหว
  • reverseDuration
    ระยะเวลาของภาพเคลื่อนไหวย้อนกลับ
  • curve
    เส้นโค้งที่จะใช้เมื่อทำให้พารามิเตอร์เคลื่อนไหว ค่าที่สอดแทรกสามารถนำมาจากการแจกแจงเชิงเส้น หรือถ้าและเมื่อระบุไว้ ก็สามารถนำมาจากเส้นโค้งได้

มาเริ่มต้นการเดินทางด้วยการสร้างแอปง่ายๆ ที่เราเรียกว่า "ยกมา" จะแสดงใบเสนอราคาแบบสุ่มทุกครั้งที่แอปเริ่มทำงาน สองสิ่งที่ควรทราบ: อันดับแรก ใบเสนอราคาทั้งหมดเหล่านี้จะถูกฮาร์ดโค้ดในแอปพลิเคชัน และประการที่สอง จะไม่มีการบันทึกข้อมูลผู้ใช้

หมายเหตุ : ไฟล์ทั้งหมดสำหรับตัวอย่างเหล่านี้สามารถพบได้ใน GitHub

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

เริ่มต้น

ควรติดตั้ง Flutter และคุณจะต้องทำความคุ้นเคยกับโฟลว์พื้นฐานก่อนที่จะดำเนินการต่อไป จุดเริ่มต้นที่ดีคือ "การใช้ Flutter ของ Google เพื่อการพัฒนาอุปกรณ์เคลื่อนที่ข้ามแพลตฟอร์มอย่างแท้จริง"

สร้างโครงการ Flutter ใหม่ใน Android Studio

เมนูโปรเจ็กต์กระพือใหม่ใน Android Studio (ตัวอย่างขนาดใหญ่)

ซึ่งจะเป็นการเปิดตัวช่วยสร้างโครงการใหม่ ซึ่งคุณสามารถกำหนดค่าพื้นฐานของโครงการได้

หน้าจอการเลือกประเภทโปรเจ็กต์ Flutter (ตัวอย่างขนาดใหญ่)

ในหน้าจอการเลือกประเภทโปรเจ็กต์ มีโปรเจ็กต์ Flutter หลายประเภท แต่ละโปรเจ็กต์รองรับสถานการณ์เฉพาะ สำหรับบทช่วยสอนนี้ ให้เลือก Flutter Application แล้วกด Next

ตอนนี้คุณต้องป้อนข้อมูลเฉพาะโครงการ เช่น ชื่อและเส้นทางของโครงการ โดเมนบริษัท และอื่นๆ ดูภาพด้านล่างได้เลยครับ

หน้าจอการกำหนดค่าแอปพลิเคชัน Flutter (ตัวอย่างขนาดใหญ่)

เพิ่มชื่อโปรเจ็กต์ เส้นทาง Flutter SDK ตำแหน่งโปรเจ็กต์ และคำอธิบายโปรเจ็กต์เพิ่มเติม กด ถัดไป

หน้าจอชื่อแพ็คเกจแอปพลิเคชัน Flutter (ตัวอย่างขนาดใหญ่)

แต่ละแอปพลิเคชัน (ไม่ว่าจะเป็น Android หรือ iOS) ต้องมีชื่อแพ็คเกจที่ไม่ซ้ำกัน โดยทั่วไปแล้ว คุณใช้โดเมนเว็บไซต์ของคุณกลับด้าน เช่น com.google หรือ com.yahoo กด เสร็จสิ้น เพื่อสร้างแอปพลิเคชัน Flutter ที่ใช้งานได้

โครงการตัวอย่างที่สร้างขึ้น (ตัวอย่างขนาดใหญ่)

เมื่อสร้างโปรเจ็กต์แล้ว คุณจะเห็นหน้าจอที่แสดงด้านบน เปิดไฟล์ main.dart (เน้นในภาพหน้าจอ) นี่คือไฟล์แอปพลิเคชันหลัก โปรเจ็กต์ตัวอย่างเสร็จสมบูรณ์ในตัวเอง และสามารถรันได้โดยตรงบนอีมูเลเตอร์หรืออุปกรณ์จริงโดยไม่ต้องดัดแปลงใดๆ

แทนที่เนื้อหาของไฟล์ main.dart ด้วยข้อมูลโค้ดต่อไปนี้:

 import 'package:animated_widgets/FirstPage.dart'; import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animated Widgets', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.redAccent, ), home: FirstPage(), ); } }

รหัสนี้จะล้างไฟล์ main.dart โดยเพียงแค่เพิ่มข้อมูลง่ายๆ ที่เกี่ยวข้องกับการสร้างแอปใหม่ คลาส MyApp ส่งคืนอ็อบเจ็กต์: วิดเจ็ต MaterialApp ซึ่งมีโครงสร้างพื้นฐานสำหรับการสร้างแอปที่สอดคล้องกับดีไซน์ Material ในการทำให้โค้ดมีโครงสร้างมากขึ้น ให้สร้างไฟล์ dart ใหม่สองไฟล์ภายในโฟลเดอร์ lib : FirstPage.dart และ Quotes.dart

ไฟล์ FirstPage.dart (ตัวอย่างขนาดใหญ่)

FirstPage.dart จะมีรหัสทั้งหมดที่รับผิดชอบองค์ประกอบภาพทั้งหมด (วิดเจ็ต) ที่จำเป็นสำหรับแอปที่เสนอราคาของเรา ภาพเคลื่อนไหวทั้งหมดได้รับการจัดการในไฟล์นี้

หมายเหตุ : ต่อมาในบทความ ข้อมูลโค้ดทั้งหมดสำหรับวิดเจ็ตแบบเคลื่อนไหวแต่ละรายการจะถูกเพิ่มลงในไฟล์นี้เป็นรายการย่อยของวิดเจ็ต Scaffold สำหรับข้อมูลเพิ่มเติม ตัวอย่างนี้บน GitHub อาจมีประโยชน์

เริ่มต้นด้วยการเพิ่มรหัสต่อไปนี้ใน FirstPage.dart นี่คือรหัสบางส่วนที่จะเพิ่มสิ่งอื่นในภายหลัง

 import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
ไฟล์ Quotes.dart (ตัวอย่างขนาดใหญ่)

ไฟล์ Quotes.dart มีรายการใบเสนอราคาแบบฮาร์ดโค้ดทั้งหมด จุดหนึ่งที่ควรทราบที่นี่คือรายการที่เป็นวัตถุคงที่ ซึ่งหมายความว่าสามารถใช้ที่อื่นได้โดยไม่ต้องสร้างวัตถุใหม่ของคลาส Quotes สิ่งนี้ถูกเลือกโดยการออกแบบเนื่องจากรายการด้านบนทำหน้าที่เป็นยูทิลิตี้

เพิ่มรหัสต่อไปนี้ในไฟล์นี้:

 class Quotes { static const quotesArray = [ "Good, better, best. Never let it rest. 'Til your good is better and your better is best", "It does not matter how slowly you go as long as you do not stop.", "Only I can change my life. No one can do it for me." ]; }

โครงงานพร้อมแล้ว มาลุยกันเลยดีกว่า

AnimatedOpacity

หากต้องการให้แอปดูเป็นส่วนตัว ควรทราบชื่อผู้ใช้ ดังนั้นขอและแสดงปุ่มถัดไป ปุ่มนี้จะถูกซ่อนไว้จนกว่าผู้ใช้จะป้อนชื่อ และจะแสดงขึ้นอย่างสวยงามเมื่อมีการตั้งชื่อ เราต้องการแอนิเมชั่นการมองเห็นบางอย่างสำหรับปุ่ม แต่มีวิดเจ็ตสำหรับสิ่งนั้นหรือไม่ ใช่มี.

ป้อน AnimatedOpacity วิดเจ็ตนี้สร้างบนวิดเจ็ต Opacity โดยเพิ่มการสนับสนุนแอนิเมชั่นโดยนัย เราจะใช้มันอย่างไร? จำสถานการณ์ของเราไว้: เราต้องแสดงปุ่มถัดไปพร้อมการมองเห็นแบบเคลื่อนไหว เรารวมวิดเจ็ตปุ่มไว้ในวิดเจ็ต AnimatedOpacity ป้อนค่าที่เหมาะสม และเพิ่มเงื่อนไขเพื่อทริกเกอร์ภาพเคลื่อนไหว และ Flutter สามารถจัดการส่วนที่เหลือได้

 _getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), ); }
แอนิเมชั่นความทึบของปุ่มถัดไป (ตัวอย่างขนาดใหญ่)

วิดเจ็ต AnimatedOpacity มีคุณสมบัติบังคับสองประการ:

  • opacity
    ค่า 1 หมายถึงมองเห็นได้ทั้งหมด 0 (ศูนย์) หมายถึงซ่อนอยู่ ขณะเคลื่อนไหว Flutter จะสอดแทรกค่าระหว่างสุดขั้วทั้งสองนี้ คุณสามารถดูวิธีการวางเงื่อนไขเพื่อเปลี่ยนการมองเห็น ทำให้เกิดภาพเคลื่อนไหวได้
  • child
    วิดเจ็ตย่อยที่จะมีการมองเห็นเป็นภาพเคลื่อนไหว

ตอนนี้คุณควรเข้าใจว่าการเพิ่มแอนิเมชั่นการมองเห็นด้วยวิดเจ็ตโดยนัยนั้นง่ายเพียงใด และวิดเจ็ตดังกล่าวทั้งหมดเป็นไปตามแนวทางเดียวกันและใช้งานง่าย มาต่อกันที่ตอนต่อไปกันเลย

AnimatedCrossFade

เรามีชื่อผู้ใช้แล้ว แต่วิดเจ็ตยังรอการป้อนข้อมูลอยู่ ในขั้นตอนก่อนหน้านี้ เมื่อผู้ใช้ป้อนชื่อ เราจะแสดงปุ่มถัดไป ตอนนี้เมื่อผู้ใช้กดปุ่ม ฉันต้องการหยุดรับอินพุตและแสดงชื่อที่ป้อน มีหลายวิธีที่จะทำ แต่บางทีเราอาจซ่อนวิดเจ็ตอินพุตและแสดงวิดเจ็ตข้อความที่ไม่สามารถแก้ไขได้ มาลองใช้กันโดยใช้วิดเจ็ต AnimatedCrossFade

วิดเจ็ตนี้ต้องใช้ลูกสองคน เนื่องจากวิดเจ็ตข้ามระหว่างกันตามเงื่อนไขบางประการ สิ่งสำคัญอย่างหนึ่งที่ต้องจำไว้ขณะใช้วิดเจ็ตนี้คือ เด็กทั้งสองควรมีความกว้างเท่ากัน หากความสูงต่างกัน วิดเจ็ตที่สูงกว่าจะถูกตัดออกจากด้านล่าง ในสถานการณ์สมมตินี้ สองวิดเจ็ตจะถูกใช้เป็นรายการย่อย: อินพุตและเลเบล

 _getAnimatedCrossfade() { return AnimatedCrossFade( duration: Duration(seconds: 1), alignment: Alignment.center, reverseDuration: Duration(seconds: 1), firstChild: _getNameInputWidget(), firstCurve: Curves.easeInOut, secondChild: _getNameLabelWidget(), secondCurve: Curves.easeInOut, crossFadeState: showNameLabel ? CrossFadeState.showSecond : CrossFadeState.showFirst, ); }
Cross-fading ระหว่างวิดเจ็ตอินพุตและวิดเจ็ตชื่อ (ตัวอย่างขนาดใหญ่)

วิดเจ็ตนี้ต้องการชุดพารามิเตอร์บังคับอื่น:

  • crossFadeState
    สถานะนี้หาว่าเด็กคนไหนที่จะแสดง
  • firstChild
    ระบุลูกคนแรกสำหรับวิดเจ็ตนี้
  • secondChild
    ระบุลูกคนที่สอง

AnimatedAlign

ณ จุดนี้ ป้ายชื่อจะอยู่ตรงกลางหน้าจอ ด้านบนจะดูดีขึ้นมาก เนื่องจากเราต้องการให้ตรงกลางหน้าจอแสดงเครื่องหมายคำพูด พูดง่ายๆ ก็คือ การจัดตำแหน่งของวิดเจ็ตป้ายชื่อควรเปลี่ยนจากตรงกลางเป็นด้านบน และคงจะดีไม่น้อยถ้าจะทำให้การเปลี่ยนการจัดแนวนี้เคลื่อนไหวไปพร้อมกับแอนิเมชั่น cross-fade ก่อนหน้านี้ มาทำกัน

และเช่นเคย สามารถใช้เทคนิคหลายอย่างเพื่อให้บรรลุสิ่งนี้ เนื่องจากวิดเจ็ตป้ายชื่อถูกจัดกึ่งกลางอยู่แล้ว การจัดตำแหน่งให้เคลื่อนไหวจึงง่ายกว่าการจัดการค่าบนและด้านซ้ายของวิดเจ็ตอย่างมาก วิดเจ็ต AnimatedAlign เหมาะสำหรับงานนี้

ในการเริ่มแอนิเมชั่นนี้ จำเป็นต้องมีทริกเกอร์ จุดประสงค์เดียวของวิดเจ็ตนี้คือเพื่อทำให้การเปลี่ยนแปลงการจัดตำแหน่งเคลื่อนไหว ดังนั้นมันจึงมีคุณสมบัติเพียงไม่กี่อย่าง: เพิ่มรายการย่อย ตั้งค่าการจัดตำแหน่ง ทริกเกอร์การเปลี่ยนแปลงการจัดตำแหน่ง เท่านี้ก็เรียบร้อย

 _getAnimatedAlignWidget() { return AnimatedAlign( duration: Duration(seconds: 1), curve: Curves.easeInOut, alignment: alignTop ? Alignment.topLeft : Alignment.center, child: _getAnimatedCrossfade(), ); }
แอนิเมชั่นการจัดตำแหน่งของวิดเจ็ตชื่อ (ตัวอย่างขนาดใหญ่)

มีคุณสมบัติบังคับเพียงสองประการ:

  • เด็ก:
    เด็กที่จะมีการปรับเปลี่ยนการจัดตำแหน่ง
  • การจัดตำแหน่ง:
    ค่าการจัดตำแหน่งที่จำเป็น

วิดเจ็ตนี้เรียบง่ายมาก แต่ผลลัพธ์ก็สวยงาม ยิ่งกว่านั้น เราได้เห็นแล้วว่าเราสามารถใช้วิดเจ็ตแอนิเมชั่นสองวิดเจ็ตเพื่อสร้างแอนิเมชั่นที่ซับซ้อนยิ่งขึ้นได้อย่างง่ายดายเพียงใด นี่คือความสวยงามของวิดเจ็ตแอนิเมชั่น

AnimatedPadding

ตอนนี้ เรามีชื่อผู้ใช้ที่ด้านบน เคลื่อนไหวได้อย่างราบรื่นโดยไม่ต้องใช้ความพยายามมาก โดยใช้วิดเจ็ตเคลื่อนไหวประเภทต่างๆ มาเติมคำทักทาย “สวัสดี” ก่อนชื่อ การเพิ่มวิดเจ็ตข้อความที่มีค่า “สวัสดี” ที่ด้านบนจะทำให้วิดเจ็ตข้อความทักทายซ้อนทับกัน ดูเหมือนรูปภาพด้านล่าง

วิดเจ็ตคำทักทายและชื่อทับซ้อนกัน (ตัวอย่างขนาดใหญ่)

จะเกิดอะไรขึ้นถ้าวิดเจ็ตข้อความชื่อมีช่องว่างภายในด้านซ้าย การเพิ่มช่องว่างภายในด้านซ้ายจะได้ผลอย่างแน่นอน แต่เดี๋ยวก่อน: เราสามารถเพิ่มช่องว่างภายในด้วยแอนิเมชั่นได้ไหม ใช่ และนั่นคือสิ่งที่ AnimatedPadding ทำ เพื่อให้ทั้งหมดนี้ดูดีขึ้นมาก ให้วิดเจ็ตข้อความทักทายจางลงและช่องว่างภายในของวิดเจ็ตข้อความชื่อเพิ่มขึ้นในเวลาเดียวกัน

 _getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), ); }

เนื่องจากแอนิเมชั่นด้านบนควรเกิดขึ้นหลังจากการจัดตำแหน่งแอนิเมชั่นก่อนหน้าเสร็จสิ้นเท่านั้น เราจึงต้องชะลอการทริกเกอร์แอนิเมชันนี้ พูดนอกเรื่องสั้น ๆ นี่เป็นช่วงเวลาที่ดีที่จะพูดถึงกลไกยอดนิยมเพื่อเพิ่มความล่าช้า Flutter มีเทคนิคหลายอย่าง แต่ตัวสร้าง Future.delayed เป็นหนึ่งในวิธีการที่ง่ายกว่า สะอาดกว่า และอ่านง่ายกว่า ตัวอย่างเช่น ในการรันโค้ดหลังจาก 1 วินาที:

 Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum); });

เนื่องจากทราบระยะเวลาการหน่วงแล้ว (คำนวณจากระยะเวลาของภาพเคลื่อนไหวก่อนหน้า) ภาพเคลื่อนไหวจึงสามารถทริกเกอร์ได้หลังจากช่วงเวลานี้

 // Showing “Hi” after 1 second - greetings visibility trigger. _showGreetings() { Future.delayed(Duration(seconds: 1), () { setState(() { showGreetings = true; }); }); } // Increasing the padding for name label widget after 1 second - increase padding trigger. _increaseLeftPadding() { Future.delayed(Duration(seconds: 1), () { setState(() { increaseLeftPadding = true; }); }); }
แอนิเมชั่นการเติมของวิดเจ็ตชื่อ (ตัวอย่างขนาดใหญ่)

วิดเจ็ตนี้มีคุณสมบัติบังคับเพียงสองคุณสมบัติ:

  • child
    ลูกในวิดเจ็ตนี้ ซึ่งจะมีการเติมช่องว่างภายใน
  • padding
    จำนวนพื้นที่ที่จะเพิ่ม

AnimatedSize

ทุกวันนี้ แอพใดๆ ที่มีแอนิเมชั่นบางประเภทจะรวมการซูมเข้าหรือออกจากองค์ประกอบภาพเพื่อดึงดูดความสนใจของผู้ใช้ (โดยทั่วไปเรียกว่าแอนิเมชั่นการปรับขนาด) ทำไมไม่ใช้เทคนิคเดียวกันที่นี่? เราสามารถแสดงคำพูดที่สร้างแรงบันดาลใจให้ผู้ใช้โดยซูมเข้าจากตรงกลางหน้าจอ ให้ฉันแนะนำคุณเกี่ยวกับวิดเจ็ต AnimatedSize ซึ่งช่วยให้เอฟเฟกต์การซูมเข้าและซูมออก ควบคุมโดยการเปลี่ยนขนาดของลูก

วิดเจ็ตนี้แตกต่างจากวิดเจ็ตอื่นๆ เล็กน้อยเมื่อพูดถึงพารามิเตอร์ที่จำเป็น เราต้องการสิ่งที่ Flutter เรียกว่า "Ticker" Flutter มีเมธอดที่จะแจ้งให้อ็อบเจ็กต์ทราบทุกครั้งที่มีเหตุการณ์เฟรมใหม่เกิดขึ้น ถือได้ว่าเป็นการส่งสัญญาณว่า “ลงมือเลย! … ทำมันตอนนี้! … ทำมันตอนนี้! …”

วิดเจ็ต AnimatedSize ต้องการคุณสมบัติ — vsync — ซึ่งยอมรับผู้ให้บริการทิกเกอร์ วิธีที่ง่ายที่สุดในการรับผู้ให้บริการทิกเกอร์คือการเพิ่ม Mixin ในชั้นเรียน มีการใช้งานผู้ให้บริการทิกเกอร์พื้นฐานสองแบบ: SingleTickerProviderStateMixin ซึ่งจัดเตรียมทิกเกอร์เดียว และ TickerProviderStateMixin ซึ่งมีหลายอย่าง

การใช้งานเริ่มต้นของ Ticker ใช้เพื่อทำเครื่องหมายเฟรมของแอนิเมชั่น ในกรณีนี้จะใช้อย่างหลัง เพิ่มเติมเกี่ยวกับมิกซ์อิน

 // Helper method to create quotes card widget. _getQuoteCardWidget() { return Card( color: Colors.green, elevation: 8.0, child: _getAnimatedSizeWidget(), ); } // Helper method to create animated size widget and set its properties. _getAnimatedSizeWidget() { return AnimatedSize( duration: Duration(seconds: 1), curve: Curves.easeInOut, vsync: this, child: _getQuoteContainer(), ); } // Helper method to create the quotes container widget with different sizes. _getQuoteContainer() { return Container( height: showQuoteCard ? 100 : 0, width: showQuoteCard ? screenWidth - 32 : 0, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text(quote, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14),), ), ), ); } // Trigger used to show the quote card widget. _showQuote() { Future.delayed(Duration(seconds: 2), () { setState(() { showQuoteCard = true; }); }); }
แอนิเมชั่นการปรับขนาดของวิดเจ็ตคำพูด (ตัวอย่างขนาดใหญ่)

คุณสมบัติบังคับสำหรับวิดเจ็ตนี้:

  • vsync
    ผู้ให้บริการทิกเกอร์ที่จำเป็นในการประสานงานการเคลื่อนไหวและการเปลี่ยนแปลงเฟรม<
  • child
    เด็กที่มีการเปลี่ยนแปลงขนาดจะเป็นภาพเคลื่อนไหว

แอนิเมชั่นการซูมเข้าและซูมออกได้รับการฝึกฝนอย่างง่ายดาย

AnimatedPositioned

ยอดเยี่ยม! คำพูดซูมเข้าจากตรงกลางเพื่อดึงดูดความสนใจของผู้ใช้ เกิดอะไรขึ้นถ้ามันเลื่อนขึ้นจากด้านล่างในขณะที่ซูมเข้า? มาลองดูกัน. การเคลื่อนไหวนี้เกี่ยวข้องกับการเล่นตำแหน่งของวิดเจ็ตใบเสนอราคาและการเปลี่ยนแปลงคุณสมบัติของตำแหน่ง AnimatedPositioned เป็นผู้สมัครที่สมบูรณ์แบบ

วิดเจ็ตนี้จะเปลี่ยนตำแหน่งของเด็กโดยอัตโนมัติในช่วงเวลาที่กำหนดเมื่อใดก็ตามที่ตำแหน่งที่ระบุเปลี่ยนแปลง จุดหนึ่งที่ควรทราบ: ใช้งานได้ก็ต่อเมื่อวิดเจ็ตหลักเป็น "สแต็ก" วิดเจ็ตนี้ค่อนข้างเรียบง่ายและใช้งานง่าย มาดูกัน.

 // Helper method to create the animated positioned widget. // With position changes based on “showQuoteCard” flag. _getAnimatedPositionWidget() { return AnimatedPositioned( duration: Duration(seconds: 1), curve: Curves.easeInOut, child: _getQuoteCardWidget(), top: showQuoteCard ? screenHeight/2 - 100 : screenHeight, left: !showQuoteCard ? screenWidth/2 : 12, ); }
ตำแหน่งที่มีอนิเมชั่นการปรับขนาดของคำพูด (ตัวอย่างขนาดใหญ่)

วิดเจ็ตนี้มีคุณสมบัติบังคับเพียงหนึ่งเดียว:

  • child
    วิดเจ็ตที่จะเปลี่ยนตำแหน่ง

หากไม่ควรเปลี่ยนขนาดของเด็กตามตำแหน่ง ทางเลือกที่มีประสิทธิภาพมากกว่าสำหรับวิดเจ็ตนี้คือ SlideTransition

นี่คือแอนิเมชั่นที่สมบูรณ์ของเรา:

รวมวิดเจ็ตภาพเคลื่อนไหวทั้งหมดเข้าด้วยกัน (ตัวอย่างขนาดใหญ่)

บทสรุป

แอนิเมชั่นเป็นส่วนสำคัญของประสบการณ์ผู้ใช้ แอพแบบคงที่หรือแอพที่มีแอนิเมชั่นที่ไม่เหมาะสมไม่เพียงลดการรักษาผู้ใช้ แต่ยังทำให้ชื่อเสียงของนักพัฒนาในการส่งมอบผลลัพธ์ด้วย

ทุกวันนี้ แอพยอดนิยมส่วนใหญ่มีแอนิเมชั่นเล็กๆ น้อยๆ เพื่อทำให้ผู้ใช้พึงพอใจ คำติชมแบบเคลื่อนไหวต่อคำขอของผู้ใช้ยังสามารถดึงดูดให้ผู้ใช้เข้าไปสำรวจเพิ่มเติมได้อีกด้วย Flutter มีคุณสมบัติมากมายสำหรับการพัฒนาข้ามแพลตฟอร์ม รวมถึงการรองรับที่หลากหลายสำหรับแอนิเมชั่นที่ลื่นไหลและตอบสนองได้ดี

Flutter มีการสนับสนุนปลั๊กอินที่ยอดเยี่ยมซึ่งช่วยให้เราใช้แอนิเมชั่นจากนักพัฒนารายอื่นได้ ตอนนี้ได้ครบกำหนดเป็นเวอร์ชัน 1.9 ด้วยความรักมากมายจากชุมชน Flutter จะต้องดีขึ้นในอนาคต ฉันว่าตอนนี้เป็นเวลาที่ดีในการเรียนรู้ Flutter!

แหล่งข้อมูลเพิ่มเติม

  • “วิดเจ็ตแอนิเมชั่นและโมชั่น” Flutter Docs
  • “ความรู้เบื้องต้นเกี่ยวกับแอนิเมชั่น,” Flutter Docs
  • Flutter Codelabs

หมายเหตุบรรณาธิการ : ขอบคุณมากสำหรับ Ahmad Awais สำหรับความช่วยเหลือของเขาในการทบทวนบทความนี้