[Flutter] 플루터 화면 이동하기
안녕하세요.
오늘은 지난시간에 이어 플루터에서 화면이동을 하는 방법에 대해 알아보겠습니다.
(지난 실습은 아래 URL에서 확인하실 수 있습니다.)
https://in-idea.tistory.com/63
안드로이드에서 엑티비티가 쌓이는 방식과 마찬가지로 플루터에서도 Navigator를 이용하여 화면 즉 Screen을 스택구조로 쌓아 관리할 수 있습니다.
화면을 쌓는 메소드는 Navigator.push메소드로, 쌓을 화면을 정의할 Screen을 생성하는 방법부터 알아보겠습니다.
class DetailScreen extends StatelessWidget {
DetailScreen({required this.book});
final Book book;
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: '도서 상세',
home: Scaffold(
appBar: AppBar(
title: Row(
children: [
IconButton(
// color: Colors.black,
icon: const Icon(
Icons.arrow_circle_left,
color: Colors.white,
),
onPressed: () {
Navigator.pop(context);
}),
Text('도서 상세'),
],
)),
body: DetailPage(book: book),
),
);
}
}
class DetailPage extends StatefulWidget {
const DetailPage({Key? key, required this.book}) : super(key: key);
final Book book;
@override
State<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.fromLTRB(4, 8, 4, 8),
child: Text(widget.book.title,
textAlign: TextAlign.left,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20)),
),
Text(
widget.book.imgSrc,
style: TextStyle(color: Colors.grey),
),
Row(
children: [
Expanded(
child: Image.network(widget.book.imgSrc),
),
],
)
],
),
);
}
}
지난시간의 도서 예제에 추가로 위와같은 스크린 + 페이지 + 스테이트 구조를 만들어줍니다.
여기서 안드로이드의 intent를 통해 화면간 데이터를 주고받는 방식과 유사하게 화면에 대한 정의 시 특정 객체를 매개변수로 받을 수 있습니다.
안드로이드에서는 intent를 통해 사용자가 직접 정의한 커스텀객체를 주고받을 때 Percelable 또는 Serializable 클래스를 상속받아 정의하는 절차가 필요하지만 플루터는 이같은 절차가 별도로 필요하지 않았다는 점이 인상깊었습니다.
그리고 현재 화면을 스택에서 내보내기 위해 Navigator.pop메소드를 Appbar의 IconButton에 정의해줍니다.
다음은 화면을 불러올 차례이지요 ??
지난 예제 listView의 아이템인 BookTile을 정의하면서 GestureDetector를 넣어두었던 것 기억하시나요 ??
여기 onTap메소드를 다음과 같이 수정합니다.
onTap: () {
final result = Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(book: this._book)),
);
},
아래는 전체 코드입니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'My Flutter Example'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Book> books = [
Book(
id: 1,
title: "하얼빈",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3368498/33684983664.20220808092115.jpg?type=w300"),
Book(
id: 2,
title: "역행자",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3255028/32550285396.20220914160250.jpg?type=w300"),
Book(
id: 3,
title: "잘될 수밖에 없는 너에게",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3410601/34106011618.20220818093442.jpg?type=w300"),
Book(
id: 4,
title: "데뷔 못 하면 죽는 병 걸림 1부 초판 한정 굿즈박스 세트",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3449237/34492373619.20220905183722.jpg?type=w300"),
Book(
id: 5,
title: "불편한 편의점 2",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3368499/33684998621.20220811101806.jpg?type=w300"),
Book(
id: 6,
title: "파친코 2",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3393982/33939827618.20220808182811.jpg?type=w300"),
Book(
id: 7,
title: "불편한 편의점",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3244499/32444990070.20220527030724.jpg?type=w300"),
Book(
id: 8,
title: "파친코 1",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3343571/33435716826.20220728093341.jpg?type=w300"),
Book(
id: 9,
title: "원씽",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3247335/32473353629.20220527033429.jpg?type=w300"),
Book(
id: 9,
title: "그대만 모르는 비밀",
imgSrc:
"https://shopping-phinf.pstatic.net/main_3434574/34345747674.20220830140856.jpg?type=w300")
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: EdgeInsets.all(10.0),
alignment: Alignment.topCenter,
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: books.length,
itemBuilder: (ctx, index) {
if (index == books.length) return SizedBox();
return BookTile(books[index]);
})));
}
}
class BookTile extends StatelessWidget {
BookTile(this._book);
final Book _book;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey)),
),
child: GestureDetector(
onTap: () {
//async await
final result = Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(book: this._book)),
);
},
child: Row(
children: [
Expanded(
flex: 1, //아래 Expanded와 크기 비율 설정
child: Container(
padding: EdgeInsets.all(8),
child: Image.network(
//네트워크 이미지를 불러와 이미지 삽입
this._book.imgSrc,
height: 100,
),
),
),
Expanded(
flex: 2,
child: Column(
children: [
Container(
padding: EdgeInsets.all(4),
alignment: Alignment.centerLeft,
child: Text(
_book.title,
),
),
Container(
padding: EdgeInsets.all(4),
alignment: Alignment.centerLeft,
child: Text(
"출처 : " + _book.imgSrc,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
))
],
),
));
}
}
class Book {
int id;
String title;
String imgSrc;
Book({required this.id, required this.title, required this.imgSrc});
Map<String, dynamic> toMap() {
return {'id': id, 'title': title, 'imgSrc': imgSrc};
}
factory Book.fromJson(Map<String, dynamic> json) {
return Book(id: json['id'], title: json['title'], imgSrc: json['imgSrc']);
}
}
class DetailScreen extends StatelessWidget {
DetailScreen({required this.book});
final Book book;
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: '도서 상세',
home: Scaffold(
appBar: AppBar(
title: Row(
children: [
IconButton(
// color: Colors.black,
icon: const Icon(
Icons.arrow_circle_left,
color: Colors.white,
),
onPressed: () {
Navigator.pop(context);
}),
Text('도서 상세'),
],
)),
body: DetailPage(book: book),
),
);
}
}
class DetailPage extends StatefulWidget {
const DetailPage({Key? key, required this.book}) : super(key: key);
final Book book;
@override
State<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.fromLTRB(4, 8, 4, 8),
child: Text(widget.book.title,
textAlign: TextAlign.left,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20)),
),
Text(
widget.book.imgSrc,
style: TextStyle(color: Colors.grey),
),
Row(
children: [
Expanded(
child: Image.network(widget.book.imgSrc),
),
],
)
],
),
);
}
}
자 이제 여러분의 예제도 화면간 이동이 정상적으로 수행되시나요 ??
이제 각 아이템을 클릭하면 다음과 같이 화면이동이 수행될 것입니다.
플루터에서 화면이동을 활용하는 방법은 여러가지인데요, 특정 스크린에 별칭을 붙혀 필요한 데에 여러번 재활용할 수 있도록 정의할 수도 있습니다. 이번 예제에서 사용한 Navigation 관련자료에서 이 방법들에 대해 소개되어 있으니 참고하여 보시면 좋을 것 같습니다.
https://flutter-ko.dev/docs/cookbook/navigation/navigation-basics
https://flutter-ko.dev/docs/cookbook/navigation/named-routes
오늘은 플루터에서 화면간 이동을 하는 방법에 대해 알아보았습니다.
다음시간에는 리스트뷰를 Endless하고 PageNation한 구조로 만드는 방법에 대해 알아보도록 하겠습니다.(예제는 계속 이어집니다.)