Animations
animation สองตระกูล
หัวข้อที่มีชื่อว่า “animation สองตระกูล”animation ใน Flutter แยกเป็นสองตระกูล และการเลือกให้ถูกคือทักษะเกือบทั้งหมด:
- Implicit — คุณเปลี่ยนค่า Flutter animate ไปหา ค่าใหม่ให้อัตโนมัติ ไม่มี controller ไม่มี lifecycle ให้จัดการ
- Explicit — คุณขับ animation เองด้วย
AnimationControllerควบคุมได้เต็มที่ (repeat, reverse, sync หลายค่า, ตอบสนอง gesture) แลกกับ ticker และการ dispose
เริ่มที่ implicit แล้วค่อยไป explicit เมื่อ implicit ทำสิ่งที่ต้องการไม่ได้
Implicit: animate ไปยังค่าใหม่
หัวข้อที่มีชื่อว่า “Implicit: animate ไปยังค่าใหม่”widget ตระกูล AnimatedFoo (AnimatedContainer, AnimatedOpacity, AnimatedPadding, …) รับ duration และ animate ทุกครั้งที่ property เป้าหมายเปลี่ยน
AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, width: _expanded ? 200 : 100, // เปลี่ยนค่านี้ (เช่นใน setState)... height: 100, color: Colors.blue, // ...แล้ว Flutter จะ animate จาก width เดิมไป width ใหม่อย่างลื่น)TweenAnimationBuilder generalize สิ่งนี้: ให้ Tween กับค่าเป้าหมาย จะ animate ค่าและ rebuild builder ของคุณ — implicit animation สำหรับ property ใด ๆ
Explicit: ขับ animation เอง
หัวข้อที่มีชื่อว่า “Explicit: ขับ animation เอง”AnimationController ผลิตค่าจาก 0 ถึง 1 ตลอด duration โดยถูก tick ด้วย vsync Tween map 0..1 ไปยัง range ของคุณ และ AnimatedBuilder rebuild เฉพาะส่วนที่ animate
class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin { late final AnimationController _c = AnimationController( vsync: this, // ticker (เหตุที่ต้องใช้ mixin) duration: const Duration(seconds: 1), )..repeat(reverse: true);
@override void dispose() { _c.dispose(); super.dispose(); } // dispose controller เสมอ
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _c, builder: (context, child) => Opacity(opacity: _c.value, child: child), child: const FlutterLogo(), // build ครั้งเดียว ไม่ rebuild ทุก tick ); }}flowchart LR ctrl["AnimationController tick 0 ถึง 1 ผ่าน vsync"] --> tween["Tween / Curve map 0..1 ไปยัง range ของคุณ"] tween --> ab["AnimatedBuilder rebuild เฉพาะส่วนที่ animate"] ab --> ui["widget update ทุก tick"]
สองกฎที่กันบั๊กที่พบบ่อย: controller ต้องมี vsync (mix in SingleTickerProviderStateMixin) และคุณต้อง dispose() controller ทิ้ง — controller ที่ leak จะ tick ต่อไปเรื่อย ๆ ส่งส่วนที่ไม่เปลี่ยนเป็น child เพื่อไม่ให้ถูก rebuild ทุก tick
Curve (Curves.easeInOut, Curves.elasticOut, …) reshape เวลาเชิงเส้นให้เป็นการเคลื่อนไหวที่เป็นธรรมชาติ ทั้งสองตระกูลรับ curve ได้ การใส่ curve คือความต่างระหว่างการ slide แบบกลไกกับการเคลื่อนไหวที่รู้สึกว่าออกแบบมา