web analytics

Flutter : รู้จักกับ Hero Widget

cove

 

สวัสดีผู้อ่านครับ บทความนี้เป็นตอนต่อเนื่องจาก เรื่อง Route Transition ซึ่งในตอนก่อนผมได้พาไปทำการสร้าง Custom Route Transition ที่ทำ Animation ขณะเปลี่ยนหน้าในแบบต่างๆ ในบทความนี้จะพามาลองเล่นเรื่อง Hero Widget ซึ่งมีความเกี่ยวเนื่องกับ Route Transition เพราะเป็นเรื่องที่จะใช้คู่กัน หากยังไม่ได้อ่าน บทความเรื่อง Route Transition แนะนำให้อ่านก่อนนะครับ

Flutter : การทำ Custom Route Transition

 

รู้จักกับ Hero Widget

Hero Widget คือ widget ที่สามารถทำ animation ระหว่างเปลี่ยนหน้าได้ โดยผมจะขอเรียก การเคลื่อนที่ไปยังหน้าใหม่ของ Hero ว่า “บิน” ตัวอย่าง ตามภาพด้านล่างนี้เลย

a1

 

การใช้งาน แค่กำหนด tag ให้มัน จะเป็นอะไรก็ได้ แค่ tag ของ Hero ในหน้า 1 ต้องตรงกับหน้า 2

 

สร้างหน้าแรก พร้อมกับ  Hero ที่มี child เป็น widget ที่ต้องการ
ในตัวอย่างนี้ผมใช้ Icon รูปเครื่องบิน

 

จากนั้นสร้าง หน้าที่สอง ที่มี Hero โดยใช้ tag เหมือนกับหน้าแรก

 

เพิ่มให้ สามารถกดที่ไอคอน แล้วเปลี่ยนหน้าไปหน้าที่ 2
ในที่นี้ผมใช้ SlideTransition แบบ custom ที่เคยเขียนไว้ในตอนก่อนหน้านี้ และกำหนดระยะเวลา transition 3 วินาที

 

ผลที่ได้คือ ไอคอนเครื่องบิน เคลื่อนที่ระหว่างเปลี่ยนหน้า และมายังตำแหน่งใหม่ในหน้าที่ 2 ด้วยระยะเวลา 3 วินาทีนั่นเอง
ไม่ยากเลย

a1

 

FlightShuttleBuilder

มาลองปรับแต่ง Hero กันเพิ่มเติม คือ การเปลี่ยน Widget ขณะที่มันกำลังบินอยู่
แค่เพิ่ม flightShuttleBuilder ให้ Hero แล้ว return Widget ที่ต้องการ

 

เช่น ผมเปลี่ยนไอคอนระหว่างบิน เป็นไอคอนเมฆ แต่เมื่อไปถึงปลายทาง ก็จะเป็นรูปที่ตั้งไว้ใน Hero ตามปกติ

a1

 

PlaceholderBuilder

เราสามารถกำหนด widget ที่อยู่ตำแหน่งเดิม ขณะ Hero เปลี่ยนหน้าได้ โดยใช้ placeholderBuilder

a2

 

Transition Flight

จากที่เราใช้ Hero ตามปกติ animation transition มันก็จะเลื่อนๆตามปกติ เราสามารถเพิ่ม transition ให้ hero ขณะบินได้ เช่น การใช้ RotateTransition คือให้มันหมุนไปด้วยขณะบิน

a3

 

หรือว่าจะใช้ FadeTransition ก็ได้ และเรายังสามารถกำหนด Curve ของ animation ได้อีกด้วย

a4

 

 

Custom Curve

Curve ที่ Flutter มีให้ อาจจะยังไม่ถูกใจเราเท่าไหร่นัก เราสามารถเขียน curve ของเราเองได้ โดยการทำ Custom Curve
เริ่มจากสร้าง class ที่ extends Curve แล้ว override method ที่ชื่อว่า transform() ซึ่ง method นี้จะรับ argument 1 ตัวคือเวลาขณะ animation ทำงาน (hero บินอยู่)
โดยเวลาจะมีค่าตั้งแต่ 0 วิ่งๆไปจนถึง 1 สิ่งที่เราต้องทำคือ แปลงค่าจากเวลา t ไปเป็นค่า ของ Curve ที่ต้องการ ผมขอเรียกว่าผลลัพธ์นี้ว่าค่า V ละกัน

เช่น
ถ้าใช้ Curve กับ ScaleTransition ค่า V ของเราคือ ขนาดของ Hero ขณะนั้น
ถ้าใช้ Curve กับ FadeTransition ค่า V ของเราคือ Opacity ของ Hero ขณะนั้น
พอจะเข้าใจแล้วใช่มัยครับ

เขียน class พร้อมกับ override transform เตรียมไว้

 

มาลองเล่นกัน โดยสิ่งที่เราจะทำ คือ การทำ ScaleTransition ของ Hero โดยให้ระหว่างบินจะค่อยๆขยายขึ้น และจะขยายสูงสุด 4 เท่า ณ จุดกลางทาง จากนั้นก็ค่อยๆลดลงจนขนาดปกติ

จากความรู้คณิตศาสตร์ สามารถใช้ Quadratic Function ที่เรารู้จัก กันจะอยู่ใน f(x) = ax2 + bx + c ซึ่ง function นี้ มันจะสามารถสร้าง curve โค้งๆ ได้
ถ้ากำหนด a < 0 กราฟจะคว่ำ แต่ถ้า a > 0 กราฟจะหงาย ในกรณีนี้เราต้องการกราฟคว่ำ
เพราะ เพื่อให้ -ax2 + bx = 0 เมื่อ x = 1, a=b ทำให้จุดเริ่มต้นกับจุดสิ้นสุดจะมีเท่าเท่ากัน ก็คือต้นทางกับปลายทาง มีค่าเท่ากัน
ถามว่าทำไมค่าต้องเท่ากัน เพราะ ต้นทางกับปลายทางเราต้องการให้มีขนาดเท่ากันนั่นเอง

เราจะลองนำ สมการนี้มาใช้กับ transform ของเรา
f(x) ในสมการคือ V
x ในสมการคือ t เวลา ตามที่อธิบายก่อนหน้านี้
ส่วน a และ b ในสมการคือ ตัวที่เราไม่รู้ โดยผมจะใช้เป็นค่าๆเดียวกันไปเลย คือ a = b
c ในสมการ ผมกำหนดเป็น 0 ไปก่อน เดี๋ยวมาอธิบายหลังว่าคืออะไร

เราจะได้สมการ transform ของเราแบบง่ายๆ ดังนี้

1-2

ซึ่ง Curve ของเรา ใช้กับ ScaleTransition ดังนั้น
v คือ สัดส่วนการขยายในเวลา t
t คือ เวลา ณ ขณะนั้น ซึ่งค่า t จะมีค่าระหว่าง 0 ถึง 1 ยิ่งเวลา animation เยอะ ยิ่งมีค่าละเอียดเยอะ
a คือ ตัวคูณ

โดยระหว่าง hero ของเราบินมันขยายขนาด จนถึงกึ่งกลาง หมายความว่า t = 0.5 ตามโจทย์ของเรา จะมีขนาด 4 เท่า
ดังนั้น V = 4 , t = 0.5 แก้สมการหา  a

ตัวคูณของเราคือ 16

ดังนั้นเราสามารถเขียนสมการ transform ได้ดังนี้

 

เสร็จแล้วเอา Curve ของเราไปใส่ให้กับ ScaleTransition ใน flightShuttleBuilder

 

ผลที่ได้คือ Hero ของเราบินขยายขึ้น และที่จุดกึ่งกลาง จะมีขนาด 4 เท่า จากนั้นก็ย่อเล็กลง ตามสมการที่เราคำนวณไว้

a5

 

เพื่อให้เห็นภาพมากขึ้น ลอง print ค่า t และ V ใน method transform จะเห็นค่า t วิ่งจาก 0….1 และที่ t=0.5 ค่า v มีค่า 4 ตามที่เราคำนวณ และหลังจากนั้น V ก็จะลดลง เข้าใกล้ 0

2

 

ทีนี้จะเห็นปัญหา ของการ scale ด้วยสมการ -ax2 + ax  คือปลายทางมันย่อจนหายไปเลย นั่นก็เพราะว่า เมื่อ t เข้าใกล้ 1 ค่า V ของเราจะเข้าใกล้ 0 ทำให้มันเล็กลงจนเหมือนหายไป

a6

 

วิธีแก้คือเราต้อง เพิ่มค่าต่ำสุดให้มัน โดยการ +1 ต่อท้ายเป็นค่าคงที่ นั่นก็คือค่า c ในสมการ
จะได้ สมการใน transform ดังนี้

 

ลองรัน

a7

 

HeroFlightDirection

ในตอนนี้ animation ขณะบินของ Hero ขาไปและขากลับจะเป็นแบบเดียวกัน
ถ้าเราอยากปรับแต่งให้ต่างกัน สามารถใช้ direction ใน flightShuttleBuilder มาเช็คได้ว่าเป็นแบบไหน
HeroFlightDirection.push คือ กรณีหน้า 1 ไปหน้า 2
HeroFlightDirection.pop คือ กรณีย้อนกลับ จาก หน้า 2 ไปหน้า 1

a8

 

Custom RectTween

มาถึงอีกเรื่องที่ซับซ้อนไปอีกหน่อย คือการกำหนดเส้นทางการบินของ Hero ของเราเอง ซึ่งจากปกติ Flutter จะมี default คือเคลื่อนที่ไปตรงๆแบบที่เราเห็น

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

การเคลื่อนของ Rect จากต้นทางไปปลายทาง จะใช้ RectTween ที่มี method ชื่อว่า lerp ทำงานคล้าย transform มันก็คือการอธิบายว่าเวลา ณ ขณะนี้ Rect มันอยู่ตำแหน่งไหนั่นเอง สิ่งที่เราต้องทำ คือ ให้เราแปลงค่าเวลา t เป็นค่าที่เราต้องการ คุ้นๆใช้มัย? ใช่แล้ว มันคือค่า V นั่นเอง โดยในกรณีนี้ค่า V ของเราคือ offset บนจอ พอเรากำหนด V ได้ ตัว Hero จะเคลื่อนที่ตาม Rect ที่เรากำหนด ดังนั้นหน้าที่ของเรา คือ เราต้องแปลงค่า t ให้เป็น offset ที่ต้องการให้แสดง โดย t จะมีค่า 0…1 เหมือนเดิม

ตัวอย่างรูปด้านล่างคือ Hero ที่มี RectTween (เส้นทาง) ตามปกติ

9

 

ในตัวอย่างนี้ เราต้องการปรับแต่งเส้นทางของ Hero เป็นแบบนี้

4

 

การกำหนด RectTween ให้กับ Hero คือต้องไปกำหนด RectTween ที่ Hero ปลายทาง (ย้ำว่าปลายทางนะ)
กล่าวคือ ถ้าต้องการให้ไอคอนเคลื่อนบินบินจากหน้า 1 ไปหน้า 2 ด้วยเส้นทาง RectTween ของเราเอง
เราต้องไปกำหนด RectTween ให้ Hero ในหน้า 2

วิธีใช้ คือ เพิ่ม createRectTween ใน Hero โดยมันจะเป็น function ที่มี 2 argument คือ Rect ของ Hero ตัวต้นทาง กับของปลายทาง (ปลายทางคือตัว Hero มันเองนี่แหละ)

 

จากนั้น เรามาเขียน class สำหรับ RectTween ของเราเอง โดยการ extends RectTween
ซึ่งผมเลือกใช้ Curve แบบกราฟ sin เพราะว่ากราฟมันจะได้เลี้ยวๆ แบบที่เราต้องการ

วิธีการคือ method ที่ชื่อว่า lerp จะมีค่า t คือเวลา ณ ขณะนั้น มาให้ เราก็แปลง t ให้เป็น offset
โดยฟังชันก์ sin จะรับค่าเป็น radian ดังนั้นเลยต้องใช้ radian , 1 รอบวงกลม คือ 6.28319 rad (2)
ถ้าอยากให้เลี้ยวแนวนอนเยอะก็เพิ่มตัวคูณให้ x อยากให้เลี้ยวแนวตั้งเยอะก็เพิ่มตัวคูณ y นั่นเอง

 

จากนั้นก็เอา RectTween ของเราไปกำหนดใน Hero

 

ลองรันจะเห็นว่า Hero เคลื่อนที่ตามกราฟ sin
รูปซ้ายคือ Hero ต้นทางไม่ได้ใส่ scaleTransition ส่วนรูปขวาคือมี scaleTransition ที่ทำไว้ก่อนหน้านี้

a9 a10

 

ลองเพิ่ม transition อีกตัวให้กับ Hero ต้นทาง คือการนำ transition ซ้อนเข้าไปเป็น child ของอีกตัว
โดยตัวอย่างนี้ ผมเพิ่ม RotateTransition เข้าไป

 

ผลคือ Hero ของเรา Scale + Rotate + Sin Curve RectTween !!!!!

a11

 

สรุป

Hero Widget ถือว่าเป็นของที่เรียกว่าเจ๋งมาก สามารถทำ transition ของ widget ขณะเปลี่ยนหน้าได้แบบง่ายๆ การกำหนด transition ก็ไม่มีอะไรซับซ้อน แต่ส่วนที่จะยุ่งยากนิดนึงคือ การ Custom Curve และ Custom RectTween ที่ต้องใช้ความรู้ทางคณิตศาสตร์นิดหน่อย หวังว่าบทความนี้จะทำให้ผู้อ่านสามารถนำเรื่อง Hero ไปประยุกต์ใช้กับงานต่างๆนะครับ

 

Credit
https://medium.com/flutter-community/a-deep-dive-into-hero-widgets-in-flutter-d34f441eb026