일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Dialog
- tabbar
- scroll
- ScrollView
- livedata
- drift
- Button
- appbar
- textview
- viewmodel
- Compose
- Kotlin
- 안드로이드
- Coroutines
- 앱바
- android
- LifeCycle
- CustomScrollView
- 계측
- data
- TEST
- Flutter
- activity
- DART
- textfield
- binding
- 앱
- 테스트
- intent
- Navigation
- Today
- Total
Study Record
[Flutter] 화면 전환하기 (Navigator) 본문
🎁 Navigator Class
Navigator 위젯은 스택 규칙(Last In First Out)에 따라 위젯들을 관리한다. 모바일 앱은 '화면', 혹은 '페이지'라 불리는 전체 화면 요소가 있는데 이걸 Flutter에서는 Route라고 부른다. Navigator는 이 Route 객체를 스택으로 관리한다. 앱에서 우리가 보는 화면은 Route Stack의 최상단에 있는 Route이고 다른 화면으로 전환하는 것은 Route Stack에 전환할 화면의 정보가 담긴 Route를 추가(push) 한 것이다. Route Stack에서 최상단에 있는 Route를 제거하면(pop) 그다음 상위 Route로 전환되면서 이전 화면으로 되돌아간다.
HomeScreen → TestScreen → FinalScreen 순으로 추가됐으면 pop 으로 제거하면 FinalScreen 이 없어지고 TestScreen, HomeScreen 순으로 사라진다.(마지막에 추가된 요소가 제일 먼저 나간다.)
Route는 일반적인 화면뿐 아니라 다이얼로그나 대화상자 등을 포함한다. 따라서 화면에 다이얼로그가 띄워졌다면 Route Stack에 다이얼로그 정보가 담긴 Route 가 추가(push)된 것이다.
void main() {
runApp(const MaterialApp(home: MyAppHome()));
}
MaterialApp()의 home 인자는 Route Stack에 MyAppHome() 정보가 포함된 Route 가 추가하는 역할을 한다. 따라서 앱을 실행했을 때 첫 페이지는 MyAppHome 화면이 된다.
😶 push
push 함수로 Route Stack 에 push 할 수 있다. 즉, 화면 전환을 할 수 있다.
Ex.) TestScreen 으로 전환
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context){
return TestScreen();
})
);
가고 싶은 화면으로 전환할 때는 Navigator.of(context).push(...) 에서 원하는 화면 위젯(TestScreen)을 넣으면 된다.
※ Navigator.of(context) 의 의미는 context와 관련된 상위 위젯트리의 Navigator를 가져온다는 의미이다.
1. settings (값 넘겨주기)
Navigator.of(context).push<int>(MaterialPageRoute(
builder: (BuildContext context) {
return TestScreen();
},
settings: const RouteSettings(
// arguments: true
/*arguments: {
"firstArgument": true,
"twoArgument": 45,
"threeArgument": "세번재 인자"
}*/
arguments: TestArgument(false, 200)
)
));
// arguments 예시를 위한 class
class TestArgument{
bool argumentBool = true;
int argumentInt = 333;
TestArgument(this.argumentBool, this.argumentInt);
}
MaterialPageRoute 의 settins에 RouteSettings의 arguments(Object?)에 값을 넣어주면 된다. arguements에 들어가는 값은 Object? 이므로 bool, int, String, dictionary, List 등 어떤 타입도 가능하다.
이렇게 전달한 값를 화면에서 ModalRoute.of(context)?. settings.argument로 받아 사용하면 된다.
class TestScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final argument = ModalRoute.of(context)?.settings.arguments as TestArgument?;
print("argument : ${argument?.argumentInt} ${argument?.argumentBool}");
return Scaffold(
body: ...
);
}
경로의 이름을 미리 설정해서 화면 전환을 시도할 수 있다.
void main() {
runApp(MaterialApp(
home: const MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => TestScreen(textWidget: Text('page A')),
'/b': (BuildContext context) => TestScreen(textWidget: Text('page B')),
'/c': (BuildContext context) => TestScreen(textWidget: Text('page C')),
},
));
}
home 이라고 되어있는 MyAppHome()의 경로 이름은 '/'이다. 미리 MaterialApp()의 routes 인자에 k/ey, value 형식으로 key는 경로 이름 value는 경로 정보를 저장해 놓고,
Navigator.of(context).pushNamed(String routeName, {Object? arguments});
// ex)
Navigator.of(context).pushNamed("/a", arguments: true);
pushNamed에 미리 설정한 경로이름(key)으로 화면전환할 수 있다. agruments 인자로 똑같이 값을 전달할 수 있다. 화면에서 값을 받아 사용할 때 똑같이 ModalRoute.of(context)?. settings.arguments를 사용한다.
class TestScreen extends StatelessWidget {
Widget textWidget;
TestScreen({required this.textWidget, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final argument = ModalRoute.of(context)?.settings.arguments as TestArgument?;
print("argument : ${argument?.argumentInt} ${argument?.argumentBool}");
return Scaffold(
body: ...
);
}
😶 pop
Navigator를 이용해 Route Stack에서 최상위 Route를 제거하러면(이전 화면으로 돌아가기) Navigator.of(context). pop()을 사용하면 된다.
Navigator.of(context).pop();
※ 예시 (HomeScreen <--> TestScreen의 경우)
> main()
void main() {
runApp(
MaterialApp(
home: HomeScreen(),
),
);
}
> HomeScreen()
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: ElevatedButton(
onPressed: (){
onStartTestScreen(context);
},
child: const Text("TestScreen 전환"),
),
)),
);
}
void onStartTestScreen(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context){
return TestScreen();
})
);
}
}
> TestScreen()
import 'package:flutter/material.dart';
class TestScreen extends StatelessWidget {
const TestScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: ElevatedButton(
onPressed: (){
onBackPressed(context);
},
child: const Text("뒤로 가기"),
),
)),
);
}
void onBackPressed(BuildContext context) {
Navigator.of(context).pop();
}
}
😶 pop으로 이전 화면으로 돌아갈 때 값 전달하기
현 화면에서 이전 화면으로 돌아갈 때, 현 화면에서 pop()에 값을 전달하면 Navigator.of(context). push(...)의 리턴값으로 돌려받을 수 있다. pop에 전달할 값은 int, bool, List 등 어떤 타입도 가능하다.
// HomeScreen -> TestScreen
// TestScreen()
class TestScreen extends StatelessWidget {
int param;
TestScreen({required this.param, Key? key}) : super(key: key);
......
void onBackPressed(BuildContext context) {
// 이전 화면에 인자 전달하기
Navigator.of(context).pop(156);
// Navigator.of(context).pop(TestArgument(true, 200));
}
}
// HomeScreen()
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
......
void onStartTestScreen(BuildContext context) async {
// 이전 값 받기
final resultValue = await Navigator.of(context).push<int>(
MaterialPageRoute(builder: (BuildContext context){
return TestScreen(param: 145);
})
);
print("TestScreen 에서 받은 값 : $resultValue");
}
}
여기서 onStartTestScreen()을 async 키워드로 비동기로 실행하고 await 키워드로 resultValue 값을 받지 않으면 TestScreen()에서 return 값으로 주는 156을 받을 수 없다. TestScreen()을 실행하고 다시 HomeScreen()으로 돌아오기 전에 resultValue 값이 결정되고 print() 함수가 실행되기 때문이다.
resultValue는 push <intt>에서 int로 값을 받을 것이라고 알 수 있지만 TestScreen()에서 return 값을 안 줄 수도 있다. 따라서 resultValue는 null 이 될 수도 있다.
🎁 여러 가지 push, pop
😶 pushReplacement() , pushReplacementNamed()
기본 push 연산은 Route Stack에 추가하는 것으로 HomeScreen에서 TestScreen으로 push 하면 경로가 HomeScreen → TestScreen 으로 쌓이고 현재 화면인 TestScreen에서 pop 하면 HomeScreen으로 돌아온다. 하지만 pushReplacement는 현재 화면을 대체한다. 따라서 HomeScreen → TestScreen이고 현재 화면이 TestScreen 상태에서 TestReplaceScreen으로 pushReplacement를 하면 HomeScreen → TestReplaeScreen 이 되는 셈이다.
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => RouteThreeScreen(),
),
);
pushReplacementNamed() 도 pushReplacement()와 같은 기능을 하지만 Named 방식을 사용하는 것만 다르다.
Navigator.of(context).pushReplacementNamed('/three');
😶 pushAndRemoveUntil(), pushNamedAndRemoveUntil()
pushAndRemoveUntil(Route newRoute, bool Function(Route ) predicate)
새로운 화면으로 전환할 때 predicate에 의해 현재 Route Stack의 최상위 Route을 검사하여 false 이면 삭제하고 그다음 Route를 검사하여 true를 리턴하거나 Route Stack에 요소가 없을 때까지 반복한다.
예를 들어,
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => TestScreen2()),
(route) => false
);
이런 식으로 모든 route를 false로 return 하게 되면 TestScreen2()만 Route Stack에 남게 된다. 따라서 TestScreen2에서 뒤로 가기 버튼을 누르면 바로 종료된다.
최초 화면이자 home 이 MyAppHomeScreen이고 Route Stack 이 MyAppHomeScreen → T1 Screen → T2 Screen 일 때, 현재 화면인 T2 Screen에서 다음과 같이 pushAndRemoveUntil()을 하면,
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => T3Screen()),
(route) => route.settings.name == '/',
);
Route Stack 은 MyAppHomeScreen →T3 Screen 이 된다. home의 경로는 '/' 이기 때문에 route.settings.name 이 home 일 때만 true이고 나머지는 false 이어서 MyAppHomeScreen 만 남고 다 삭제된다.
pushNamedAndRemoveUntil의 경우 새로운 화면에 대한 정보를 미리 설정해 둔 경로이름을 사용하는 것만 다르다.
void main() {
runApp(MaterialApp(
home: const MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => TestScreen(),
'/b': (BuildContext context) => TestScreen2(),
'/c': (BuildContext context) => TestScreen3(),
'/d': (BuildContext context) => TestScreen4(),
},
));
}
경로 이름이 위와 같고, MyAppHome() → TestScreen() → TestScreen2() → TestScreen3() → TestScreen4()로 화면전환한다고 할 때,
// TestScreen3()
Navigator.of(context).pushNamedAndRemoveUntil("/d", (route) {
if(route.settings.name == "/a") return true;
return false;
});
TestScreen3 이 위와 같다면, TestScreen4 화면으로 진입했을 때 Route Stack 은 MyAppHome → TestScreen() → TestScreen4()가 될 것이다. (TestScreen3 = false , TestScreen2 = false, TestScreen = true)
😶 maybePop()
Navigator.of(context).maybePop();
maybePop() 은 pop()을 할 수 있을 때는 하고 하지 못하는 경우에는 하지 않는다. 하지 못하는 경우에는 Route Stack 에 현재 화면밖에 없을 때가 될 수 있다. 첫 화면인데 pop() 을 한다면 Route Stack 에는 아무것도 없어 검은 화면이 보일 수 있다.
😶 canPop()
Navigator.of(context).canPop();
pop() 이 가능하면 true 불가능하면 false을 리턴한다. canPop() 은 pop()을 하지 않고 pop() 이 가능한지만 알려준다.
+참고 사이트
'Flutter' 카테고리의 다른 글
[Flutter] 이미지, 비디오 파일 가져오기(image_picker) (0) | 2023.02.15 |
---|---|
[Flutter] 그라데이션 효과와 BoxDecoration (0) | 2023.02.13 |
[Flutter] Button 만들기 (TextButton, ElevatedButton, OutlinedButton) (0) | 2023.02.08 |
[Flutter] const 를 사용하는 이유 (0) | 2023.02.04 |
[Flutter] theme 적용해보기 - 글꼴 (0) | 2023.02.03 |