플러터) flutter 3 - 기본 화면 구성
1. 머티리얼과 쿠퍼티노 디자인
1) 머터리얼 디자인
(1) 디버그 배너 빼기 - debugShowCheckedModeBanner
MaterialApp(
debugShowCheckedModeBanner: false
)
(2) 테마 설정하기 - Theme
- 테마 설정하기
MaterialApp( theme: ThemeData( primarySwatch: Colors.pink // 테마 : 핑크색 ) )
- 앱바 색상만 변경하기
MaterialApp( theme: ThemeData( primarySwatch: Colors.pink, appBarTheme: AppBarTheme( backgroundColor: Colors.orange, foregroundColor: Colors.black )))
✨ 코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.pink,
appBarTheme: AppBarTheme(
backgroundColor: Colors.orange,
foregroundColor: Colors.black,
),
),
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Center(
child: Column(
children: [
ElevatedButton(onPressed: () {}, child: Text('Button')),
Checkbox(value: true, onChanged: (value) {}),
Text('HelloWorld')
],
)),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add), onPressed: () {})));
}
}
2) 쿠퍼티노 디자인
import 'package:flutter/cupertino.dart'
CupertinoApp() // material 대체
(1) Platform API 활용하기
- Platform의 정적 속성(static property)
- isAndroid : 안드로이드 식별
- isFuchsia : 푸크시아 식별
- isIOS : iOS 식별
- isLinux : 리눅스 식별
- isMacOS : macOS 식별
- isWindows : 윈도우 식별
- 플랫폼 별 다른 UI 적용하기
Widget platformUI() { if (Platform.isIOS) { return CupertinoApp() } else if (Platform.isAndroid) { return MaterialApp() } }
✨ 코드 예시
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget platformUI() {
if (Platform.isIOS) {
return CupertinoApp(
debugShowCheckedModeBanner: false,
theme: CupertinoThemeData(brightness: Brightness.light),
home: CupertinoPageScaffold(
navigationBar:
CupertinoNavigationBar(middle: Text('Cupertino Title')),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CupertinoButton(
onPressed: () {},
child: Text('click'),
),
Center(child: Text('HelloWorld'))
],
)));
} else if (Platform.isAndroid) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('Material Title'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(onPressed: () {}, child: Text('click')),
Center(
child: Text('HelloWorld'),
)
],
)));
} else {
return Text('unKnown Device',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 25));
}
}
@override
Widget build(BuildContext context) {
return platformUI();
}}
3) 기기 모양에 대처하기
Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: getWidgets(),
))))
✨코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
List<Widget> getWidgets() {
List<Widget> widgets = [];
for (var i = 0; i < 100; i++) {
widgets.add(ListTile(title: Text('Hello World Item $i')));
}
return widgets;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: getWidgets(),
)),
)));
}
}
4) 스캐폴드 위젯
(1) 앱바 - appBar
- 구성 요소 설정
- leading : 왼쪽에 출력할 위젯
- title : 타이틀 위젯
- actions : 오른쪽에 사용자 이벤트를 위한 위젯들
- bottom : 앱바 하단을 구성하기 위한 위젯
- flexibleSpace : 앱바 상단과 하단 사이의 여백을 구성하기 위한 위젯
- 앱바 출력하기
Scaffold( appBar : AppBar( bottom : PreferredSize( preferredSize : const Size.fromHeight(48.0), child : Theme( data: ThemeData.from( colorScheme: ColorScheme.fromSwatch(accentColor: Colors.white) ), child: Container( height: 48.0, alignment : Alignment.center, child : Text('AppBar Bottom Text')), ), flexibleSpace: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('images/big.jpg'), fit: BoxFit.fill) ) ), title : Text('AppBar Title'), action : <Widget>[ IconButton( icon : const Icon(Icons.add_alert), onPressed : () {}, ), IconButton( icon : const Icon(Icons.phone), onPressed : () {}, )]) )))
(2) 하단 내비게이션 바 - bottomNavigationBar
Scaffold(
BottomNavigationBar : BottomNavigationBar(
type: BottomNavigationBarType.shifting, // fixed, shifting
item: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'First',
backgroundColor: Colors.green
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Second',
backgroundColor: Colors.red
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Third',
backgroundColor: Colors.purple
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Fourth',
backgroundColor: Colors.pink
)
],
currentIndex: _selectedIndex,
selectedItemColor : Colors.amber[800],
onTap : _onItemTapped
))
(3) 드로어 - drawer
Scaffold(
drawer: Drawer( // endDrawer (오른쪽으로 열리게 하기)
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeadr(),
ListTile(),
ListTile()
])))
✨코드 예시
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget>[
Text(
'First Screen',
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
Text(
'Second Screen',
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
Text(
'Third Screen',
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
Text(
'Fourth Screen',
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
bottom: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Theme(
data: ThemeData.from(
colorScheme:
ColorScheme.fromSwatch(accentColor: Colors.white)),
child: Container(
height: 48.0,
alignment: Alignment.center,
child: Text('AppBar Bottom Text')),
),
),
flexibleSpace: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/big.jpg'),
fit: BoxFit.fill)),
),
title: Text('AppBar Title'),
actions: <Widget>[
IconButton(
onPressed: () {}, icon: const Icon(Icons.add_alert)),
IconButton(onPressed: () {}, icon: const Icon(Icons.phone))
]),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
// fixed, shifting
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'First',
backgroundColor: Colors.green),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Second',
backgroundColor: Colors.red),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Third',
backgroundColor: Colors.purple),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Fourth',
backgroundColor: Colors.pink)
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(color: Colors.blue),
),
ListTile(
title: Text('Item 1'),
onTap: () {},
),
ListTile(
title: Text('Item 2'),
onTap: () {},
),
],
))));
}
}
5) 커스텀 스크롤 뷰와 슬리버 앱바
- CustomScrollView : 화면의 한 영역에서 스크롤이 발생할 때 다른 영역도 함께 스크롤 되게 함
- SliverList, SliverGrid로 화면 구성
- SliverAppBar : 함께 스크롤할 화면 상단을 구현
- 커스텀 스크롤 뷰 사용/슬리버 앱바 설정
CustomScrollView( slivers: [ SliverAppBar( floating: true // 다시 나타날 때 가장 먼저 나타나야 하는지 pinned: true // 스크롤되어 접힐 때 모두 사라져야 하는지 설정 snap: true // 스크롤이 멈추었을 때 계속 나타나야 하는지 설정 ), SliverFixedExtentList() ] )
✨코드 예시
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
leading: IconButton(
icon: Icon(Icons.expand),
onPressed: () {},
),
expandedHeight: 200,
floating: true,
pinned: false,
snap: true,
elevation: 50,
backgroundColor: Colors.pink,
flexibleSpace: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/big.jpg'),
fit: BoxFit.fill)),
),
title: Text('AppBar Title'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.add_alert),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.phone),
onPressed: () {},
),
]),
SliverFixedExtentList(
itemExtent: 50.0,
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
return ListTile(title: Text('Hello World Item $index'));
}),
)
],
)));
}
}
2. 네비게이션을 이용한 화면 전환
1) 내비게이션 사용하기
(1) 라우트 이해하기
- 플러터에서는 화면과 관련된 모든 것이 위젯
- Route : 화면을 지칭하는 객체(화면을 구성하는 다양한 정보)
- Navigator : Route객체를 화면으로 전환
- 코드 예시
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget Build(BuildContext context) { return MaterialApp( home: OneScreen() ); } // 화면 전환하기 ElevatedButton( child: Text('Go Two Screen'), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => TwoScreen()), ); } ) // 이전 화면으로 돌아가기 ElevatedButton( onPressed: () { Navigator.pop(context); }, child: Text('Go back!') ) } // 앱 화면 구성 class OneScreen extends StatelessWidget { } class TwoScreen extends StatelessWidget { }
(2) 라우트 이름으로 화면 전환하기
- Navigator.push() 함수로 화면을 전환하는 방법은 여러 화면일 시 비효율적일 수 있음
- 코드 예시
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( initialRoute : '/', routes: { '/': (context) => OneScreen(), '/two' : (context) => TwoScreen(), '/three' : (context) => ThreeScreen() } ); } ElevatedButton( child: Text('Go Two Screen'), onPressed: () { Navigator.pushNamed(context, '/two'); } ) }
✨코드 예시
- main.dart
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( initialRoute: '/one', routes: { '/one': (context) => OneScreen(), '/two': (context) => TwoScreen(), '/three': (context) => ThreeScreen(), '/four': (context) => FourScreen(), },);}}
- TwoScreen예시
import 'package:flutter/material.dart'; class TwoScreen extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('TwoScreen'), ), body: Container( color: Colors.green, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'TwoScreen', style: TextStyle(color: Colors.white, fontSize: 30), ), ElevatedButton( onPressed: () { Navigator.pushNamed(context, '/three'); }, child: Text('Go Three')), ElevatedButton( onPressed: () { Navigator.pop(context); }, child: Text('Pop')), ], ), )))); } }
(3) 화면 전환할 때 데이터 전달하기
- push() 함수로 화면 전환할 때 데이터 전달
Navigator.push( context, MaterialPageRoute(builder: (context) => TwoScreen("hello")) )
- pushNamed() 함수로 화면 전환할 때 데이터 전달
ElevatedButton( onPressed: () { Navigator.pushNamed( context, '/three', arguments: 10 // 데이터 ); }, child: Text('Go Three Screen') )
- 데이터 얻기
int arg = ModalRoute.of(context)?.settings.arguments as int;
- JSON 타입으로 데이터 여러 개 전달하기
Navigator.pushNamed( context, '/three', arguments: { "arg1" : 10, "arg2" : "hello", "arg3" : User('kkang', 'seoul') // 사용자 정의 객체 전달 });
- JSON 타입의 데이터 얻기
Map<String, Object> args = ModalRoute.of(context)?.settings.arguments as Map<String, Object>;
- 데이터 얻기
- pop() 함수로 화면 전환할 때 데이터 전달
ElevatedButton( onPressed: () async { final result = Navigator.pop(context, User('kim', 'busan') // 사용자 정의 객체 전달하기 ); }, child: Text('Go Back') )
- 데이터 얻기
onPressed: () async { final result = await Navigator.pushNamed( context, '/three', ); print('result: ${(result as User).name}'); }
- 데이터 얻기
(4) 동적 라우트 등록 방법 - onGenerateRoute
- 정적 라우트 등록
routes: { '/two' : (context) => TwoScreen() }
- 동적 라우트 등록(routes와 함께 적용 가능)
onGeneratedRoute: (settings) { if (settings.name == '/two') { final arg = settings.arguments; if (arg != null) { return MaterialPageRoute( builder: (context) => TwoScreen(), settings: settings ); } else { return MaterialPageRoute( builder: (context) => TwoScreen() ); }}}
✨코드 예시
- 전송 데이터
class User { String name; String address; User(this.name, this.address); }
- main.dart
import 'package:flutter/material.dart'; import 'four_screen.dart'; import 'three_screen.dart'; import 'two_screen.dart'; import 'one_screen.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( initialRoute: '/one', routes: { '/one': (context) => OneScreen(), '/two': (context) => TwoScreen(), // '/three': (context) => ThreeScreen(), // '/four': (context) => FourScreen(), }, onGenerateRoute: (settings) { if (settings.name == '/three') { return MaterialPageRoute( builder: (context) => ThreeScreen(), settings: settings); } else if (settings.name == '/four') { return MaterialPageRoute( builder: (context) => FourScreen(), settings: settings); }},);}}
- TwoScreen예시
import 'package:flutter/material.dart'; import 'user.dart'; class TwoScreen extends StatelessWidget { @override Widget build(BuildContext context) { Map<String, Object> args = ModalRoute.of(context)?.settings.arguments as Map<String, Object>; return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('TwoScreen'), ), body: Container( color: Colors.green, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'TwoScreen', style: TextStyle(color: Colors.white, fontSize: 30), ), Text( 'sendData:${args['arg1']}, ${args['arg2']}, ${(args['arg3'] as User).name}'), ElevatedButton( onPressed: () { Navigator.pushNamed(context, '/three'); }, child: Text('Go Three')), ElevatedButton( onPressed: () { Navigator.pop(context, User('kim', 'busan')); }, child: Text('Pop')), ], ), )))); } }
(5) 네비게이터 스택 제어하기
- pushNamed() : 스택이 다층 구조로 쌓인다.
- pop() : 스택 맨 위의 라우트 객체 제거한 후 그 다음 라우트 화면 출력
- 맨 마지막 라우트였다면? 👉👉
앱 종료
- 맨 마지막 라우트 일때
앱 종료
하고 싶지 않다면, maybePop()Navigator.maybePop(context);
- 맨 마지막 라우트인지 확인하고 싶다면, canPop()
Navigator.cnaPop(context) // true or flase
- 맨 마지막 라우트였다면? 👉👉
- pushReplacementNamed(), popAndPushNamed() : 현재 위젯 대체
Navigator.pushReplacementNamed(context, '/three'); Navigator.popAndPushNamed(context, '/three');
- pushNamedAndRemoveUntill() : 원하는 위치까지 스택에서 제거한 후 화면이동
- 단순 스택 쌓기
Navigator.pushNamedAndRemoveUntill(context, '/four', (route) => true);
- 새 스택 쌓기
Navigator.pushNamedAndRemoveUntill(context, '/four', (route) => false);
- 특정 위젯까지만 남기고 추가
Navigator.pushNamedAndRemoveUntil(context, '/four', ModalRoute.withName('/one')); // OneScrean부터 다시 쌓기
- 단순 스택 쌓기
- popUntil() : 특정 위젯으로 되돌아가기
ElevatedButton( onPressed: () { Navigator.popUntil(context, ModalRoute.withName('/two')); //TwoScreen 쌓여 있는 곳까지 가기 } child: Text('Pop Until') )
2) 내비게이션 2.0 사용하기
- 내비게이션 2.0 : 웹앱 지원 및 다양한 화면 전환 기법 수요로 추가된 API를 총칭
(1) 내비게이션 2.0 기본 구조
- Router : 페이지 정보를 스택 구조로 가지는 위젯
- Page : 출력할 화면 정보
- RouterDelegate : 다양한 상황에 맞는 라우팅 처리
- RouteInformationParser : 라우팅 요청 분석
(2) 내비게이터와 페이지
- Navigator : 라우터의 스택구조를 활용하여 화면을 구성(build함수에서 반환한 위젯)
- pages : 라우터에 포함할 여러 페이지 추가 가능
- onPopPage : (Not Null) 뒤로 갈 페이지 동적 설정(AppBar가 제공하는 back-button을 누를 때 호출 됨)
- 코드 예시
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Navigator Test', home : Navigator( pages: [ MaterialPage( child : OneScreen() ) ], onPopPage : (route, result) => route.didPop(result) ))}}
- 딥 링크(deep link) : 첫 화면이 아닌 다른 화면부터 출력
- 코드 예시
class MyApp extends StatelessWidget { boll _isDeepLink = true; @override Widget build(BuildContext context) { return MaterialApp( title: 'Navigator Test', home: Navigator( pages: [ MaterialPage( child: OneScreen() ), if (_isDeepLink) MaterialPage(child: TwoScreen()) ], onPopPage: (route, result) => route.didPop(result) ));}}
- 코드 예시
✨코드 예시
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
bool _isDeepLink = true;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigator Test',
home: Navigator(pages: [
MaterialPage(child: OneScreen()),
if (_isDeepLink) MaterialPage(child: TwoScreen())
], onPopPage: (route, result) => route.didPop(result)));
}
}
class OneScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('OneScreen'),
),
body: Container(
color: Colors.red,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('OneScreen',
style: TextStyle(color: Colors.white, fontSize: 30))
],
)),
));
}
}
class TwoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TwoScreen')),
body: Container(
color: Colors.cyan,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'TwoScreen',
style: TextStyle(color: Colors.white, fontSize: 30),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Pop'))
],
))));
}
}
(3) 라우터 델리게이트와 정보 분석기
- 화면 구성이 복잡할 때 이용
- 라우터 경로 클래스
class MyRoutePath { String? id; MyRoutePath.home() : this.id = null; MyRoutePath.detail(this.id); }
- 라우터 델리게이트와 정보 분석기 등록
class _MainAppState extends State<MainApp> { @override Widget build(BuildContext context) { return MaterialApp.router( routerDelegate: MyRouterDelegate(), routeInformationParser: MyRouteInformationParser()); } }
- 라우터 정보 분석기(Route InformationParser) : 화면을 전환하는 라우트 정보를 분석해 경로(RoutePath)에 담음
- 앱의 라우팅 정보 분석 : 앱을 처음 실행하거나 라우팅될 때 정보 분석
- 앱의 라우팅 정보 저장 : 라우팅 결정된 후 현재 상태 저장
class MyRouteInformationParser extends RouteInformationParser<MyRoutePath> { // parseRouteInformation() 함수 구현 @override Future<MyRoutePath> parseRouteInformation( RouteInformation routeInformation) async { final uri = Uri.parse(routeInformation.location ?? '/'); if (uri.pathSegments.length >= 2) { var remaining = uri.pathSegments[1]; return MyRoutePath.detail(remaining); } else { return MyRoutePath.home(); } } // restoreRouteInformation() 함수 구현 @override RouteInformation restoreRouteInformation(MyRoutePath configuration) { print('restoreRouteInformation.. id : ${configuration.id}'); if (configuration.id != null) { return RouteInformation(location: '/detail/${configuration.id}'); } else { return RouteInformation(location: '/'); } } }
- 라우터 델리게이트(RouterDelegate) : 라우팅 대리자로서 정보 분석기가 분석한 경로를 받아 내비게이터를 만듬
- currentConfiguration() : build() 함수 호출 직전에 자동 호출, 해당 함수에서 만들어진 라우터 경로가 정보 분석기의 restoreRouteInformation() 함수에 전달되어 라우팅 상태 저장
- setNewRoutePath() : parseRouteInformation() 함수에서 반환한 값을 받아 라우터 델리게이트가 초기화될 때 호출(추상 클래스)
- notifyListeners() : 화면 전환환
// 상속, with 필수 class MyRouterDelegate extends RouterDelegate<MyRoutePath> with ChangeNotifier, PopNavigatorRouterDelegateMixin<MyRoutePath> { final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); // 내비게이터 구현하기 @override Widget build(BuildContext context) { return Navigator( key: navigatorKey, pages: [ MaterialPage(child: HomeScreen(_handleOnPressed)), if (selectId != null) MaterialPage(child: DetailScreen(selectId)) ], onPopPage: (route, result) {} ); } // currentConfiguration() 함수 구현 @override MyRoutePath get currentConfiguration { if (selectId != null) { return MyRoutePath.detail(selectId); } else { return MyRoutePath.home(); } } // setNewRoutePath() 함수 구현 @override Future<void> setNewRoutePath(MyRoutePath configuration) async { print('MyRouterDelegate... setNewRoutePath ... id : ${configuration.id}'); if(configuration.id != null) { selectId = configuration.id; } } // notifyListeners() 함수 구현 void _handleOnPressed(String id) { selectId = id; print('MyRouterDelegate... notifylistener call..'); notifyListeners(); } }
✨코드 구현
import 'package:flutter/material.dart';
void main() {
runApp(MainApp());
}
class MainApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MainAppState();
}
// 라우터 델리게이트, 정보 분석기 등록
class _MainAppState extends State<MainApp> {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: MyRouterDelegate(),
routeInformationParser: MyRouteInformationParser());
}
}
// 라우터 경로
class MyRoutePath {
String? id;
MyRoutePath.home() : this.id = null;
MyRoutePath.detail(this.id);
}
// 라우터 정보 분석기
class MyRouteInformationParser extends RouteInformationParser<MyRoutePath> {
// parseRouteInformation() 함수 구현
@override
Future<MyRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location ?? '/'); //location은 과거버전
if (uri.pathSegments.length >= 2) {
var remaining = uri.pathSegments[1];
return MyRoutePath.detail(remaining);
} else {
return MyRoutePath.home();
}
}
// restoreRouteInformation() 함수 구현
@override
RouteInformation restoreRouteInformation(MyRoutePath configuration) {
if (configuration.id != null) {
return RouteInformation(location: '/detail/${configuration.id}'); //location은 과거버전
} else {
return RouteInformation(location: '/'); //location은 과거버전
}
}
}
// 라우터 델리게이트
class MyRouterDelegate extends RouterDelegate<MyRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<MyRoutePath> {
String? selectId;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// currentConfiguration() 함수 구현
@override
MyRoutePath get currentConfiguration {
if (selectId != null) {
return MyRoutePath.detail(selectId);
} else {
return MyRoutePath.home();
}
}
// 내비게이터 구현하기
// notifyListeners()
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(child: HomeScreen(_handleOnPressed)),
if (selectId != null) MaterialPage(child: DetailScreen(selectId))
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
selectId = null;
notifyListeners();
return true;
});
}
// setNewRoutePath() 함수 구현
@override
Future<void> setNewRoutePath(MyRoutePath configuration) async {
if (configuration.id != null) {
selectId = configuration.id;
}
}
// notifyListeners()
void _handleOnPressed(String id) {
selectId = id;
notifyListeners();
}
}
// 홈화면
class HomeScreen extends StatelessWidget {
final ValueChanged<String> onPressed;
HomeScreen(this.onPressed);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.red,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Home Screen',
style: TextStyle(color: Colors.white, fontSize: 30),
),
ElevatedButton(
onPressed: () => onPressed('1'),
child: Text('go detail with 1')),
ElevatedButton(
onPressed: () => onPressed('2'),
child: Text('go detail with 2')),
],
),
),
));
}
}
// 디테일 화면
class DetailScreen extends StatelessWidget {
String? id;
DetailScreen(this.id);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.green,
child: Center(
child: Text('Detail Screen $id',
style: TextStyle(color: Colors.white, fontSize: 30)))));
}
}
댓글남기기