Jin's Dev Story

[Flutter] 커스텀 페이지 전환 애니메이션 본문

Web & Android/Flutter

[Flutter] 커스텀 페이지 전환 애니메이션

woojin._. 2023. 9. 23. 14:25

종류

  1. MaterialPageRoute()
  2. CupertinoPageRoute()
    1. 슬라이드 전환
    2. import 'package:flutter/cupertino.dart' 상단에 추가해야함
  3. PageRouteBuilder()
    1. 전체 커스터마이징

MaterialPageRoute()

  • 페이지 전환 애니메이션
GestureDetector(
                        child: Text(widget.data[i]['user']),
                        onTap: () {
                            Navigator.push(context,
                              MaterialPageRoute(builder: (c) => Profile())
                            );
                        },
                      ),

 

CupertinoPageRoute()

  • 오른쪽에서 왼쪽으로 보이는 애니메이션
GestureDetector(
                        child: Text(widget.data[i]['user']),
                        onTap: () {
                            Navigator.push(context,
                              CupertinoPageRoute(builder: (c) => Profile())
                            );
                        },
                      ),

 

PageRouteBuilder()

  • 오른쪽에서 왼쪽으로 서서히 보이는 애니메이션
Navigator.push(context,
  PageRouteBuilder(
    pageBuilder: (c, a1, a2) => Upload(),
    transitionsBuilder: (c, a1, a2, child) => FadeTransition(opacity: a1, child: child),
    transitionDuration: Duration(milliseconds : 500),
  ),
)
  • pageBuilder: 에는 보여줄 페이지를 return
  • transitionsBuilder: 는 파라미터 4개를 입력하고 애니메이션을 return
    • 첫째는 context (쓸데없음)
    • 둘째는 0에서 1로 증가하는 애니메이션 숫자 (새로운 페이지에 씀)
    • 셋째는 0에서 1로 증가하는 애니메이션 숫자 (기존에 보이던 페이지에 씀)
    • 넷째는 현재 보여주는 위젯
  • FadeTransition()
    • opacity: 애니메이션 숫자
    • child: 보여줄 위젯
    • 이렇게 작성하면 특정 위젯을 서서히 opacity를 0에서 1로 보여줄 수 있음
  • transitionDuration : Duration(milliseconds : 500) 이걸로 몇초간 동작할지 설정 가능
  • FadeTransition 말고 SlideTransition 써도 됨
    SlideTransition(
      position: Tween(
        begin: Offset(-1.0, 0.0),
        end: Offset(0.0, 0.0),
      ).animate(a1),
      child: child,  
    )
  • 위젯 위치 변경하려면 PositionedTransition()
  • 위젯 사이즈를 변경하려면 ScaleTransition()
  • 위젯을 가렸다가 보여주려면 SizeTransition()
  • 위젯을 회전시키려면 RotationTransition()

Hero()

  • 두 페이지 간에 똑같은 위젯이 있을 때 그걸 유지하면서 페이지 전환하고 싶은 경우 사용

전체 코드

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import './style.dart' as style;
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter/rendering.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

void main() {
  runApp(
      MaterialApp(
      theme: style.theme,
      home: MyApp()
  ));
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var tab = 0;  // 1. 현재 상태 저장
  var list = [1, 2, 3];
  var map = {'name':'john', 'age':20};
  var data = [];
  var userImage; // 유저가 삽입한 이미지 저장 공간
  var userContent; // 유저가 입력한 글 저장 공간

  saveData() async {
    var storage = await SharedPreferences.getInstance();

    var map = {'age' : 20};
    storage.setString('map', jsonEncode(map));
    var result = storage.getString('map') ?? '업는데요';
    print(jsonDecode(result)['age']);
  }

  addMyData() {
    var myData = {
      'id': data.length,
      'image': userImage,
      'likes': 5,
      'date': 'July 25',
      'content': userContent,
      'liked': false,
      'user': 'John Kim'
    };
    setState(() {
      data.insert(0, myData);
    });
  }
  setUserContent(a) {
    setState(() {
      userContent = a;
    });
  }

  addData(a) {
    setState(() {
      data.add(a);
    });
  }

  getData() async {
    var result = await http.get(Uri.parse('https://codingapple1.github.io/app/data.json'));

    var result2 = jsonDecode(result.body);
    setState(() {
        data = result2;
    });

  }

  @override
  void initState() {
    super.initState();
    saveData();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text('Instagram'),
          actions: [
            IconButton(
              icon: Icon(Icons.add_box_outlined),
              onPressed: () async {
                var picker = ImagePicker();
                var image = await picker.pickImage(source: ImageSource.gallery);
                if(image != null)  {
                  setState(() {
                    userImage = File(image.path);
                  });
                }

                Navigator.push(context,
                  MaterialPageRoute(builder: (c) => Upload( userImage : userImage, setUserContent : setUserContent, addMyData : addMyData) )
                );
              },
              iconSize: 30,
            )
          ]),
      body: [Home(data : data, addData : addData), Text('샵페이지')][tab],  // 2. state에 따라 tab 보이는 상태 변경
      bottomNavigationBar: BottomNavigationBar(
        showSelectedLabels: false,
        showUnselectedLabels: false,
        onTap: (i){
          setState(() {
            tab = i;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home_outlined), label: '홈'),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_bag_outlined), label: '샵')
        ],
      ),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key? key, this.data, this.addData}) : super(key: key);
  final data, addData;

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {

  var scroll = ScrollController();

  getMore() async {
    var result = await http.get(Uri.parse('https://codingapple1.github.io/app/more1.json'));
    var result2 = jsonDecode(result.body);

    widget.addData(result2);
  }

  @override
  void initState() {
    super.initState();
    scroll.addListener(() {   // 스크롤바 높이 측정 코드
      if(scroll.position.pixels == scroll.position.maxScrollExtent) {
        getMore();
      }
    });
  }

  @override
  Widget build(BuildContext context) {

    if (widget.data.isNotEmpty) {
      return ListView.builder(
          itemCount: widget.data.length,
          controller: scroll,
          itemBuilder: (c, i) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                widget.data[i]['image'].runtimeType == String ? Image.network(widget.data[i]['image']) : Image.file(widget.data[i]['image']),
                Container(
                  constraints: BoxConstraints(maxWidth: 600),
                  padding: EdgeInsets.all(20),
                  width: double.infinity,
                  child: Column(
                    children: [
                      GestureDetector(
                        child: Text(widget.data[i]['user']),
                        onTap: () {
                            Navigator.push(context,
                              PageRouteBuilder(pageBuilder: (c, a1, a2) => Profile(),
                                  transitionsBuilder: (c, a1, a2, child) =>
                                      FadeTransition(opacity: a1, child: child)
                              )
                            );
                        },
                      ),
                      Text('좋아요 ${widget.data[i]['likes'].toString()}'),
                      Text(widget.data[i]['date']),
                      Text(widget.data[i]['content'])
                    ],
                  ),
                )
              ],
            );
          }
      );
    } else {
      return Text('로딩중임');
    }
  }
}

class Upload extends StatelessWidget {
  const Upload({Key? key, this.userImage, this.setUserContent, this.addMyData}) : super(key: key);
  final userImage, setUserContent, addMyData;

  @override

  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(actions: [
          IconButton(onPressed: () {
            addMyData();
          }, icon: Icon(Icons.send))
        ],),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            IconButton(
                onPressed: (){
                  Navigator.pop(context);
                },
                icon: Icon(Icons.close)
            ),
            Image.file(userImage),
            Text('이미지업로드화면'),
            TextField(onChanged: (text) {  // text는 유저가 입력한 글
              setUserContent(text);  // TextField()에 입력값 변할 때마다 onChanged 안의 함수가 실행됨
            },),
          ],
        )
    );
  }
}

class Profile extends StatelessWidget {
  const Profile({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Text('프로필페이지')
    );
  }
}