Flutter : รู้จักกับ Route Transition
สวัสดีผู้อ่านครับ เราคงจะเคยทำแอปให้เปลี่ยนหน้านึงไปอีกหน้านึง ซึ่งใน Flutter ก็ทำได้ง่ายมาก แต่ว่าการทำ Animation หรือการ custom route transition ก็มีรายละเอียดที่ต้องรู้อยู่บ้าง ในบล็อกนี้จะพามารู้จักพื้นฐานของการทำ Routing ใน Flutter รวมถึงไปลองเล่น Animation route transition แบบต่างๆครับ
เริ่มต้น
สร้าง widget สำหรับหน้าแรก โดยมีปุ่มสำหรับกดเพื่อเปลี่ยนไปหน้าที่สอง
class MyFirstPage extends StatelessWidget { MyFirstPage({Key key, this.title}) : super(key: key); final String title; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Container( color: Colors.blue[100], child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'First Page', style: TextStyle(fontSize: 22), ), FlatButton( color: Colors.red[300], child: Text("Go to Second page", style: TextStyle(color: Colors.white)), onPressed: () => navigateToSecondPage(context)) ], ), ))); } navigateToSecondPage(BuildContext context) { // }
สร้างหน้าที่สอง
สร้างอีก widget class สำหรับหน้าที่ 2
class MySecondPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.red[300], title: Text("Second Page"), ), body: Container( color: Colors.red[100], child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Hello, Second Page', style: TextStyle(fontSize: 22), ), ], ), ))); } }
Navigator
การเปลี่ยนหน้าจะใช้ class ที่เรียกว่า Navigator โดยตัวมันเองจะทำหน้าที่จัดการ Stack ที่เก็บ route
ดังนั้นเลยมี method ให้เรียก push / pop
วิธีเปลี่ยนหน้าคือ push route เข้าไป
Navigator.push(context , route )
วิธีกลับหน้าเดิม คือ pop
Navigator.pop(context)
หากใช้ pushReplacement มันจะ pop อันเก่าก่อนแล้ว push อันใหม่ ทำให้อาจไม่มีปุ่ม back
Navigator.pushReplacement( ... )
MaterialPageRoute
มารู้จัก PageRoute ตัวแรก คือ MaterialPageRoute สิ่งที่ MaterialPageRoute ทำคือ เพิ่ม Animation ของ transition ให้กับ route แบบที่เราเห็นบ่อยๆใน Android
วิธีการใช้ คือ เพิ่ม function builder ที่ return Widget ที่เราต้องการให้แสดง
navigateToSecondPage(BuildContext context) { Navigator.push( context, MaterialPageRoute(builder: (context) { return MySecondPage(); })); }
CupertinoPageRoute
PageRoute อีกตัว คือ CupertinoPageRoute
ก่อนอื่น import cupertino.dart
import 'package:flutter/cupertino.dart';
สิ่งที่ CupertinoPageRoute ช่วยคือ การทำ transition เปลี่ยนหน้าแบบที่เราเห็นใน iOS
วิธีการใช้ก็คล้ายกับ MaterialPageRoute
Navigator.push( context, CupertinoPageRoute(builder: (context) { return MySecondPage(); }));
Route name
การเปลี่ยนหน้าอีกวิธีคือ การสร้างชื่อเรียกให้มัน
ก่อนอื่นสร้าง Map สำหรับเก็บ key และ route
class MyRoute { static const String ROUTE_SECOND_PAGE = "second"; static Map<String, WidgetBuilder> routes = { ROUTE_SECOND_PAGE: (context) => MySecondPage(), }; }
เพิ่ม รายชื่อ route ให้กับ MaterialApp
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( routes: MyRoute.routes, ...
วิธีการใช้คือ ผ่าน Navigator.pushNamed แล้วกำหนดชื่อ route ที่ต้องการ
Navigator.pushNamed(context, MyRoute.ROUTE_SECOND_PAGE);
จะเห็นว่า Animation ของ transition เป็นของ MaterialRoutePage ทั้งๆที่เราไม่ได้ใช้ MaterialRoutePage เลย ที่เป็นแบบนี้เพราะการใช้ route name ผ่าน MaterialApp มันจะใช้ transition แบบเดียวกับ MaterialRoutePage ให้อัตโนมัติ
ถ้าอยากได้ transition แบบ CaputinoRoutePage โดยใช้วิธีกำหนด route name ก็ต้องใช้ CaputinoApp แทนนั่นเอง ซึ่งในรายละเอียด widget จะแตกต่างจาก MaterialApp อยู่พอสมควร
PageRouteBuilder
จาก MaterialRoutePage และ CaputinoRoutePage ก็เป็นแค่ตัวอย่างการทำ route transition ที่ Flutter ทำไว้ให้ กรณีที่เราอยากทำ route transition ของเราเอง จะต้องใช้ PageRouteBuilder
ซึ่ง PageRouteBuilder ก่อนอื่นจะต้องกำหนด pageBuilder ให้มัน
Navigator.push( context, PageRouteBuilder( pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return MySecondPage(); }));
ตอนนี้จะยังไม่มี animation
SlideTransition
โดยตัวอย่างนี้ผมจะใช้ SlideTransition คือ animation การเคลื่อนที่ แล้วก็กำหนดระยะเวลา คือ 3 วินาที ซึ่งการเคลื่อนที่นั้นจะระบุด้วย Offset ตำแหน่ง (x,y) โดยใช้ตำแหน่งมุมบนซ้ายของหน้าจอเราเป็นหลัก ซึ่งโดยปกติจะอยู่ที่ตำแหน่ง (0,0)
และการ animate จากค่าหนึ่งไปอีกค่าหนึ่งจะใช้คลาสที่ชื่อว่า Tween แค่กำหนด begin , end ให้มัน
ดังนั้นถ้าผมบอกว่าให้เคลื่อนที่จาก (-1,0) ไป (0,0) จะเขียนได้แบบนี้
Tween<Offset>( begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0), )
ผลทีไ่ด้คือ
ทีนี้มาลองกำหนด animation จริงๆ โดยการเพิ่มส่วนของ transitionsBuilder ให้กับ PageRouteBuilder
ซึ่งก็ให้ return SlideTransition ตำแหน่งคือ Tween (-1,0) ไป (0,0) ส่วน child ก็คือ widget ที่ได้จาก pageBuilder
Navigator.push( context, PageRouteBuilder( pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return MySecondPage(); }, transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { return SlideTransition( position: Tween<Offset>( begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0), ).animate(animation), child: child, ); }, transitionDuration: Duration(seconds: 3)));
ลองรัน
ลองทำอีกกรณี คือ Slide จากล่างขึ้นมา ก็คือ (0,1) ไป (0,0)
Tween<Offset>( begin: Offset(0.0, 1.0), end: Offset(0.0, 0.0),
ลองใช้แบบมีทศนิยมบ้าง ดูว่าจะเกิดอะไรขึ้น
Tween<Offset>( begin: Offset(-1.0, 1.0), end: Offset(0.5, 0.5),
Curve
Animation ยังสามารถใส่ interpolator ได้ด้วย ใน Flutter เรียกว่า Curve
มันก็คือ Animation แบบมีลูกเล่น เช่น ช้าๆแล้วค่อยๆเร็วขึ้น ซึ่งมีให้เลือกอยู่หลายแบบ
SlideTransition( position: Tween<Offset>( begin: Offset(0.0, 1.0), end: Offset(0.0, 0.0), ).animate(CurvedAnimation(parent: animation, curve: Curves.linearToEaseOut)), child: child, )
สามารถลองเล่น Curve แบบต่างๆ ได้ตามนี้เลย
https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4
ScaleTransition
นอกจาก SlideTransition ก็มี transition อีกหลายแบบ เช่น scale
คือทำจากเล็กไปใหญ่ หรือใหญ่มาเล็ก โดยค่า จะเริ่มจาก 0 และ 1 คือขนาดปกติ
ScaleTransition( scale: Tween<double>( begin: 0, end:1, )
FadeTransition
Animation ที่เล่นกับ Opacity โดย 0 คือโปร่งใส และ 1 คือแสดงผลชัดเจน
FadeTransition( opacity: Tween<double>( begin: 0, end:1, )
Multi-transitions
เราสามารถนำ transition หลายๆแบบมาใช้ร่วมกันได้ โดยการซ้อน transition เป็น child ของอีก transition นึง
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { return SlideTransition( position: Tween<Offset>( begin: Offset(0.0, 1.0), end: Offset(0.0, 0.0), ).animate(animation), child: FadeTransition( opacity: Tween<double>( begin: 0, end: 1, ).animate(animation), child: child, ), ); }
ทำ route transition class ให้ใช้ง่าย
เพื่อให้การเปลี่ยนหน้ากับ transition ใช้งานง่ายขึ้น ควรสร้าง class จัดการ route transition ของเราเอง
โดย extends PageRouteBuilder
import 'package:flutter/material.dart'; class SlideEnterBottomToTop extends PageRouteBuilder{ // }
จากนั้นเพิ่ม widget , duration ให้รับ parameter จากภายนอก
class SlideEnterBottomToTop extends PageRouteBuilder { Widget child; Duration duration; SlideEnterBottomToTop( {@required this.child, this.duration = const Duration(seconds: 2)}):super(); }
แล้วก็ copy code PageRouteBuilder มาใส่ใน super ของ class เรา
class SlideEnterBottomToTop extends PageRouteBuilder { Widget child; Duration duration; SlideEnterBottomToTop( {@required this.child, this.duration = const Duration(seconds: 2)}) : super( pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return child; }, transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { return SlideTransition( position: Tween<Offset>( begin: Offset(0.0, 1.0), end: Offset(0.0, 0.0), ).animate(animation), child: child, ); }, transitionDuration: duration); }
ทีนี้ ตอนเรา push เราก็แค่เรียก route จาก class ของเราแทน ซึ่งจะมาพร้อมกับ Aniamtion transition แล้ว
Navigator.push(context, SlideEnterBottomToTop(child: MySecondPage()));
สรุป
โดยปกติการเปลี่ยนหน้าจะใช้คำสั่ง push/pop จาก Navigator ซึ่งจะเหมือนกับเพิ่ม route ลงไปใน Stack ซึ่งการสร้าง route ก็มีให้เลือก คือใช้จาก MaterialPageRoute หรือ CupertinoPageRoute ทั้งสองตัวคือ transition ที่ทาง Flutter เตรียมไว้ให้ แต่ถ้าเราอยาก custom ของเราเองก็ต้องใช้ PageRouteBuilder ที่ต้องเขียน transiton เอง โดยมี ให้เลือกหลายแบบ เช่น slide, scale, fade และอีกหลายตัว
ตอนหน้า
เนื้อหาต่อเนื่องจากเรื่องนี้ คือ Hero Widget ที่จะใช้คู่กับ Route Transition ครับ เรียนเชิญอ่านได้