web analytics

Flutter : รู้จักกับ Route Transition

cove

สวัสดีผู้อ่านครับ เราคงจะเคยทำแอปให้เปลี่ยนหน้านึงไปอีกหน้านึง ซึ่งใน 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) {
    //
  }

1

 

สร้างหน้าที่สอง

สร้างอีก 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),
                  ),
                ],
              ),
            )));
  }
}

2

 

Navigator

การเปลี่ยนหน้าจะใช้ class ที่เรียกว่า Navigator โดยตัวมันเองจะทำหน้าที่จัดการ Stack ที่เก็บ route
ดังนั้นเลยมี method ให้เรียก push / pop

3

 

วิธีเปลี่ยนหน้าคือ push route เข้าไป

Navigator.push(context , route )

วิธีกลับหน้าเดิม คือ pop

Navigator.pop(context)

 

หากใช้ pushReplacement  มันจะ pop อันเก่าก่อนแล้ว push อันใหม่ ทำให้อาจไม่มีปุ่ม back

Navigator.pushReplacement( ... )

a2

 

MaterialPageRoute

มารู้จัก PageRoute ตัวแรก คือ MaterialPageRoute สิ่งที่ MaterialPageRoute ทำคือ เพิ่ม Animation ของ transition ให้กับ route แบบที่เราเห็นบ่อยๆใน Android

วิธีการใช้ คือ เพิ่ม function builder ที่ return Widget ที่เราต้องการให้แสดง

  navigateToSecondPage(BuildContext context) {
    Navigator.push(
        context, MaterialPageRoute(builder: (context) {
          return MySecondPage();
    }));
  }

a1

 

CupertinoPageRoute

PageRoute อีกตัว คือ CupertinoPageRoute

ก่อนอื่น import cupertino.dart

import 'package:flutter/cupertino.dart';

 

สิ่งที่ CupertinoPageRoute ช่วยคือ การทำ transition เปลี่ยนหน้าแบบที่เราเห็นใน iOS
วิธีการใช้ก็คล้ายกับ MaterialPageRoute

 Navigator.push(
        context, CupertinoPageRoute(builder: (context) {
          return MySecondPage();
    }));

a3

 

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);

a1

 

จะเห็นว่า 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

a4

 

SlideTransition

โดยตัวอย่างนี้ผมจะใช้ SlideTransition คือ animation การเคลื่อนที่ แล้วก็กำหนดระยะเวลา คือ 3 วินาที ซึ่งการเคลื่อนที่นั้นจะระบุด้วย Offset ตำแหน่ง (x,y) โดยใช้ตำแหน่งมุมบนซ้ายของหน้าจอเราเป็นหลัก ซึ่งโดยปกติจะอยู่ที่ตำแหน่ง (0,0)

5a

 

และการ animate จากค่าหนึ่งไปอีกค่าหนึ่งจะใช้คลาสที่ชื่อว่า Tween แค่กำหนด begin , end ให้มัน
ดังนั้นถ้าผมบอกว่าให้เคลื่อนที่จาก (-1,0) ไป (0,0) จะเขียนได้แบบนี้

Tween<Offset>(
                  begin: Offset(-1.0, 0.0),
                  end: Offset(0.0, 0.0),
                )

 

ผลทีไ่ด้คือ

a6

 

ทีนี้มาลองกำหนด 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)));

 

ลองรัน

a5

 

 

ลองทำอีกกรณี คือ Slide จากล่างขึ้นมา ก็คือ (0,1) ไป (0,0)

 Tween<Offset>(
                  begin: Offset(0.0, 1.0),
                  end: Offset(0.0, 0.0),

a7

 

ลองใช้แบบมีทศนิยมบ้าง ดูว่าจะเกิดอะไรขึ้น

Tween<Offset>(
                  begin: Offset(-1.0, 1.0),
                  end: Offset(0.5, 0.5),

a9

 

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,
              )

a8

 

สามารถลองเล่น 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,
                )

a10

 

FadeTransition

Animation ที่เล่นกับ Opacity โดย 0 คือโปร่งใส และ 1 คือแสดงผลชัดเจน

FadeTransition(
                opacity: Tween<double>(
                  begin: 0,
                  end:1,
                )

a11

 

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,
                ),
              );
            }

a12

 

ทำ 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 ครับ เรียนเชิญอ่านได้

Flutter : Hero Widget