[Flutter] Nested Navigator

김남주·2022년 12월 25일
0

flutter

목록 보기
1/1
post-thumbnail

Flutter 공식 홈페이지에 있는 Cookbook - Create a nested navigation flow 에서 따온 움짤이다.

위 처럼 Scaffold 전체를 덮는 페이지를 push하는게 아니라 부분적으로 (위의 움짤에서는 AppBar 고정) 화면을 푸시하려면 어떻게 해야할까?
이때 필요한 것이 Nested Navigator이다!

먼저 Navigator와 같은 용어에 대해 간략하게 알아보자!

공식 문서에 따르면

A widget that manages a set of child widgets with a stack discipline.

라고 첫줄에 정의되어 있다. 우리는 모바일에서 페이지를 이동할 때, 스택처럼 페이지를 쌓고 꺼내면서 이동하게 되는데 쉽게 말해서 이 Navigator가 위와 같은 stack을 관리한다고 생각하면 된다!

The navigator manages a stack of Route objects and provides two ways for managing the stack.

즉 이 Route들이 있는 스택을 관리한다고 한다.

여기서 또 Route는 무엇인가하면

An abstraction for an entry managed by a Navigator
This class defines an abstract interface between the navigator and the "routes" that are pushed on and popped off the navigator.

Navigator가 관리하는 요소의 인터페이스 같은 것이다. 또 Most routes have visual affordances 라고 쓰여 있는데 이는 page가 push되고 pop될 때의 시각적인 애니메이션 등이 정의되어 있다는 것이다.

대표적으로 우리가 많이 쓰는 MaterialPageRoute는 platfrom-adaptive하게 전체 스크린을 transition하는 visual affordance를 갖고있는 Route이다. (dialog를 띄울때는 PopupRoute를 사용)

(참고로 아래와 같은 구조로 상속되어 있음)

Inheritance
Object → Route → OverlayRoute → TransitionRoute → ModalRoute 
PageRoute → MaterialPageRoute

여기까지 간략한 용어들을 알아봤고 다시 Nested Navigator에 대해 알아보자.

Nested Navigator를 사용하면 위와 같은 구조로 Navigator가 존재하게 된다.
Root Navigator는 MaterialApp에 기본적으로 포함되어 있는 Navigator이다.
MainPage(page1, page2)에서 Page3으로 넘어갈 때 Navigator.push를 활용하여 넘어가면 전체 페이지가 전환될 것이다. 그러나 MainPage의 page1, page2 간의 화면 전환에서 Nested Navigator를 활용한다면 MainPage의 기본 골격(위의 움짤에서는 AppBar가 해당)은 그대로 유지되고 화면전환이 될 것이다!

그러면 코드로는 어떻게 구현되어 있는지 알아보자

// Navigator를 식별해주는 key
final _navigatorKey = GlobalKey<NavigatorState>();

// 이런 식으로 root navigator를 이용하지 않고, GlobalKey로 식별한
// Nested Navigator를 활용하여 화면전환
void _onDiscoveryComplete() {
  _navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage);
}

void _onDeviceSelected(String deviceId) {
  _navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage);
}

void _onConnectionEstablished() {
  _navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage);
}


Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: _isExitDesired,
    child: Scaffold(
			// 이 코드 부분에서는 정의 되어 있지 않지만 AppBar를 리턴하는 함수이고, 이렇게 AppBar를 고정
      appBar: _buildFlowAppBar(),
			// body부분에 Navigator를 할당하여 이 부분만 페이지 전환이 일어나도록 함⭐️⭐️⭐️
      body: Navigator(
				// 이 Navigator를 식별하기 위한 key를 할당
        key: _navigatorKey,
        initialRoute: widget.setupPageRoute,
        onGenerateRoute: _onGenerateRoute,
      ),
    ),
  );
}

// push가 일어날때 RouteSetings에 따라 Route를 생성해주는 함수
Route _onGenerateRoute(RouteSettings settings) {
  late Widget page;
  switch (settings.name) {
    case routeDeviceSetupStartPage:
      page = WaitingPage(
        message: 'Searching for nearby bulb...',
        onWaitComplete: _onDiscoveryComplete,
      );
      break;
    case routeDeviceSetupSelectDevicePage:
      page = SelectDevicePage(
        onDeviceSelected: _onDeviceSelected,
      );
      break;
    case routeDeviceSetupConnectingPage:
      page = WaitingPage(
        message: 'Connecting...',
        onWaitComplete: _onConnectionEstablished,
      );
      break;
    case routeDeviceSetupFinishedPage:
      page = FinishedPage(
        onFinishPressed: _exitSetup,
      );
      break;
  }
	
// MaterialPageRoute에 담아서 리턴
  return MaterialPageRoute<dynamic>(
    builder: (context) {
      return page;
    },
    settings: settings,
  );
}

references:

profile
안녕하세요

0개의 댓글