Flutter | มาลองทำแอป Icon Showcase || Part II อนิเมชั่น
เริ่ม
กลับมาต่อกับพาร์ทที่ 2 ครั้งนี้เราจะมาเพิ่มในส่วนของอนิเมชั่น ถ้าใครอยากกลับไปดูการดีไซน์ หรือคำอธิบายของ Widget แต่ละตัวสามารถไปดูได้ที่พาร์ทที่ 1 ตามลิงค์ด้านล่างนี้เลย
หรือใครอยากต่อยอดจากอันก่อนหน้า มาลองทำเฉพาะส่วนอนิเมชั่นเพิ่ม ก็สามารถไปโหลดจาก Github ได้ โดยคำอธิบายต่างๆในครั้งนี้เราก็จะเอาโค็ดจากครั้งที่แล้วมาทำต่อ
เป้าหมายสุดท้ายเรา จะทำให้ออกมาตามนี้
เป้าหมายของเราคือ จะสร้างอนิเมชั่นสำหรับเวลาเปลี่ยนหน้า ให้มันดูลื่นไหล โดยเราจะทำ 2 จุด
- ที่ Card แต่ละอันใน ListView
- ที่ Icon Menu ที่อยู่ด้านซ้ายของ Appbar
และสุดท้าย เราจะแก้ transition ระหว่างเปลี่ยนหน้าให้ดูกลมกลืนขึ้นด้วย
อนิเมชั่นสำหรับ Card
เราเริ่มจากง่ายๆก่อน เอา Widget Hero ไปหุ้ม Card ทั้งในหน้า main.dart
และ detail_page.dart
แล้วให้ตัง tag ของทั้งคู่ให้เหมือนกัน ซึ่งเราก็ตั้งให้เป็นชื่อ title ของไอคอนแต่ละอัน
แล้วผลลัพธ์ที่ได้
ผลลัพธ์ก็ไม่ได้ออกมาดูแย่เลย แต่ถ้าเราอยากไปให้สุดกว่านี้ เราก็จะทำให้หัวข้อกับไอคอน ขยับตามไปด้วยจากตำแหน่งในหน้าก่อนหน้าไปหน้าถัดไป แต่ปัญหาคือ จู่ๆ เราจะครอบ Hero ให้ Text กับ Icon เลยไม่ได้ เพราะตอนนี้ทั้งคู่เป็น Widget ลูกหลาน ใน Hero ก่อนหน้าไปแล้ว เราไม่สามารถ ครอบ Hero ซ้อน Hero ได้ (อ้างอิง)
ทางแก้ของเราคือ แทนที่จะมอง Card ของเราเป็นชิ้นเดียว เราใช้ Stack แยกเป็น 3 ชั้นซ้อนกันแทน จากบนลงล่าง หัวข้อ ไอค่อน พื้นหลัง ซึ่งจะเพิ่มงานให้เราหน่อย แต่เพื่อความงาม ก็จัดไป
ที่ main.dart
เราจะแทนที่ Card ด้วย Stack หรือแก้โค๊ตกลายเป็นด้านล่างเลย
ที่ detail_page.dart
เราก็ทำคล้ายๆ กัน
ผลลัพธ์เริ่มคล้ายที่เราตั้งเป้าแล้ว
แต่!!!! เห็นตัวอักษรแสดงแปลกๆ ไหม เพราะ อีกปัญหากับ Hero คือมันไม่ได้ถูกดีไซน์ให้ใช้กับ Text ตั้งแต่แรกเริ่ม แต่ก็ใช่ว่าจะแก้ไม่ได้ ทางออกก็มีแบบหยาบ กับทำให้เนียนเลย แบบหยาบก่อน เราจะเอา Text ทั้งในหน้า main.dart
และ detail_page.dart
ไปคลุมด้วย Widget Material และแทรกพารามีเตอร์ type: MaterialType.transparency
เพิ่มเข้าไป ก็จะพอแก้ขัดไปคร่าวๆได้
ประมาณนี้ อันนี้ไปลองเอาเอง วิธีนี้จะพบเฉพาะปัญหา Text Overflow เวลา transition ทำงาน
ทางแก้ที่ดีกว่า แต่ก็ยาวกว่าเยอะคือ เราจะต้องไปแก้ flightShuttleBuilder ของ Hero ซึ่งเราก็ต้องมาทำอนิเมชั่นให้กับ Text ของเราเอง Hero จะได้เอาตัวนี้ไปใช้แทน
หลักการของเรา คือ เราจะสร้าง Widget ให้ Text ที่จะมี 4 สถานะ enlarge(กำลังขยายขนาด), enlarged(ค้างที่ขนาดใหญ่), shrink(กำลังย่อส่วน), shrunk(ค้างที่ขนาดเล็ก) เราจะใช้ชื่อเหล่านี้เรียกสถานะ ดังนั้น เราเลยสร้างไฟล์ใหม่ขึ้นมา จะประกอบไปด้วย enum พวกนี้ ชื่อไฟล์ว่า view_state.dart
view_state.dart
เรามาสร้างตัว Widget ให้ Text ต่อ สร้างไฟล์ใหม่ชื่อว่า title_hero_flight.dart
title_hero_flight.dart
เราค่อยๆมา อธิบายไปทีละส่วนกัน
- class DestinationTitleContent จะทำหน้าที่รับผิดชอบในการสร้าง Widget Text ณ เวลาหนึ่งๆ โดยจะรับ ข้อความ ขนาดฟอร์ต จำนวนบรรทัด overflow isOverflow สี
- class DestinationTitle จะทำหน้าที่เอาข้อความที่สร้างจาก class DestinationTitleContent สร้างข้อความนิ่ง หรืออนิเมชั่น ขึ้นกับ ViewState ที่ส่งมา คลาสนี้จึงจำเป็นต้องรับ ViewState ขนาดฟอร์ตทั้งเล็ก และใหญ่เข้ามาด้วย สำหรับ ViewState.enlarge กับ ViewState.shrink ที่มีอนิเมชั่น AnimationController จะเข้ามาจัดการส่วนนี้ ซึ่งขอละคำอธิบายในส่วนนี้ไปก่อน
ใครตามมาถึงจุดนี้แล้วงงเป็นพิเศษ ก็แนะนำให้อย่าพึ่งไปคิดไรมาก ก็อปทั้งไฟล์ title_hero_flight.dart
นี้ไปใช้ก่อนก็ได้
เราจะกลับไปที่ main.dart
กับ detail_page.dart
กันต่อ เอาของใหม่ของเรามาใช้งาน
แก้ Hero ที่มี Text ให้เป็นตามนี้
สังเกตว่าใน flightShuttleBuilder เราจะ return DestinationTitle ที่มี ViewState ไม่ enlarge ก็ shrink เพราะ flightShuttleBuilder จะถูกเรียกขณะที่มี transition เปลี่ยนหน้าเกิดขึ้น ส่วน child เราจะส่ง DestinationTitle ที่มี ViewState enlarged กับ shrank ตามหน้าที่ต้องแสดง
เสร็จเรียบร้อยในส่วนของ Card แล้ว ไปกันต่อ
อนิเมชั่นสำหรับ Icon Menu
ในส่วนนี้ก็ไม่ค่อยยุ่งยากมากแล้ว แต่ต้องใช้ AnimationController ดังนั้นมาเริ่มกันเลยที่ main.dart
เราจะเพิ่มที่ขีดเส้นใต้ 2 อัน และกล่อง 3 กล่องลงไปใน class _MyHomePageState
ฟังก์ชั่น _initAnimationController()
จะทำหน้าที่ประกาศค่าแรกเริ่มให้ _animationController ของเรา โดยเราตั้งเวลาเล่นไว้ที่ 500 ms เมื่อสร้างเสร็จก็เอาฟังก์ชั้นนี้ไปใส่ใน initState()
และสุดท้ายอย่าลืม dispose _animationController ของเราที่ ฟังก์ชั่น dispose()
ด้วย
หลังจากสร้าง _animationController เสร็จ เราก็เอามันมาใช้เวลาจะเปลี่ยนหน้าไป-กลับ แต่ก่อนหน้านั้น เราจำเป็นต้องเปลี่ยนไอคอน จากภาพนิ่งเฉยๆให้ มีอนิเมชั่นด้วย ซึ่ง Flutter ก็มีให้เราใช้ AnimatedIcon ดังนั้นเราก็เปลี่ยนเลย
แล้วสุดท้ายไปเพิ่ม _animationController.forward(from: 0.0);
ตอนก่อนกำลังเปลี่ยนไปหน้า DetailPage
และ _animationController.reverse(from: 1.0);
เมื่อกลับมาจากหน้าก่อนหน้า แต่ก่อนจะรัน ก็ต้องมีหลักการเช็คเล็กน้อย
และทำแบบเดียวกันในหน้า detail_page.dart
ด้วย
มีความแตกต่างเล็กน้อยเมื่อ initState()
เราจะ _animationController.forward()
ไปเลย
แล้วอย่าลืมเปลี่ยนไอคอนเดิมให้กลายเป็น AnimatedIcon ด้วย
จากจุดนี้จะพบว่าอนิเมชั่นเราจะทำงานแล้ว แต่ตำแหน่งของมันยังไม่เคลื่อนตาม ไม่พอยังโดนทับอีก
ทางแก้รอบนี้ง่ายเลย เอา Widget Hero หุ้ม AnimatedIcon ทั้งสองหน้า และอย่าลืมตั้ง tag ให้เหมือนกัน
แค่นี้ก็เรียบร้อย
(แถม) เพื่อความสมูทอีกนิด แก้ transition เปลี่ยนหน้า
เราจำเป็นต้องสร้าง transition ของเราเอง สร้างไฟล์ใหม่เรียก fade_page_route.dart
แล้วก็อปแปะเลย
เราสามารถเปลี่ยนไปใช้ transition อื่นได้ แต่เราจะใช้ FadeTransition เพื่อความกลมกลืน นอกจากนี้ เราสามารถแก้ Duration กับ Curve ของ transition ได้ โดยเราจะตั้ง Duration ไว้ 550 millisecond และใช้ Curves.easeIn
พร้อมแล้ว เราก็จะใช้แทนที่ MaterialPageRoute ในหน้า main.dart
import เข้ามาแล้วเปลี่ยน MaterialPageRoute เป็น FadePageRoute
ตอนนี้ แอปเราก็ออกมาดูดีแล้ว
จบไปอีกแอปนึง หวังว่าการแนะนำ Widget เหล่านี้ และการเล่นกับ Hero และ AnimationController จะช่วยทำให้รู้จักการทำอนิเมชั่นมากขึ้นไม่มากก็น้อย อย่างไรก็ตามแอปนี้ยังมีอีกหลายจุดให้เก็บตกอีก อย่างเช่น Icon ที่อยู่ใน Card ไม่ได้สเกลขนาดตามเมื่อเปลี่ยนหน้า หรือสี Icon บน Appbar ไม่ได้ค่อยๆเปลี่ยนตาม ก็ลองไปทำเพิ่มเองได้นะ 55 หรือใครลองทำตามแล้วไม่เข้าใจส่วนไหนก็พิมพ์คำถามลงด้านล่างได้เลย
ถ้าเกิดพยายามแก้โค๊ดตามแล้วงง หรืออยากข้ามเอาโค๊ดเต็มมาเทียบเลย ก็โหลดจากลิงค์ด้านล่างได้เลย