Props
ReactNative 에서 대부분의 컴포넌트는 매겨변수나 속성을 props로 전달함
Flutter에서는 매개변수가 있는 생성자에서 받은 속성을 final로 표시된 지역변수나 함수에 할당함
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
// Flutter class CustomCard extends StatelessWidget { CustomCard({@required this.index, @required this.onPress}); final index; final Function onPress; @override Widget build(BuildContext context) { return Card( child: Column( children: <Widget>[ Text('Card $index'), FlatButton( child: const Text('Press'), onPressed: this.onPress, ), ], )); } } ... //Usage CustomCard( index: index, onPress: () { print('Card $index'); }, )
로컬 저장소
많은 데이터가 아니라면, 키-값 쌍 형태의 저장소인 shared_preference 플러그인으로 기본 타입(booleans, floats, int, long, string) 데이터를 읽고 쓸 수 있음
- ios에선 NSUserDefaults, Android에선 SharedPreferences를 감싸고 있음
- 다음과 같이 의존성을 추가한 후, import하여 사용
1 2 3 4
dependencies: flutter: sdk: flutter shared_preferences: ^0.4.3
1 2
// Dart import 'package:shared_preferences/shared_preferences.dart';
데이터 저장 : SharedPreferences 클래스의 setter 메소드 사용
- setInt, setBool, setString 과 같은 방식의 다양한 기본형 타입에서 사용가능하다.
데이터 읽기 : SharedPreferences 클래스의 getter 메소드 사용
- getInt, getBool, getString 과 같은 함수 사용
1
2
3
4
5
6
7
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter');
prefs.setInt('counter', ++_counter);
setState(() {
_counter = _counter;
});
Routing
- 플러터에서는 새로운 화면도 위젯이다.
- Navigator 위젯을 통해 이동
스택 네비게이션
Route 위젯 : 앱의 화면 또는 페이지를 추상화 한 것
Navigator 위젯 : route를 관리하는 위젯
- 스택을 사용하여 자식 위젯(Route 객체)들을 관리함
- Navigator.push, Navigator.pop 같은 메서드를 제공
- Route 목록은 MaterialApp 위젯에서 지정가능
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Flutter class NavigationApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( ... routes: <String, WidgetBuilder>{ '/a': (BuildContext context) => usualNavscreen(), //Named route 지정 '/b': (BuildContext context) => drawerNavscreen(), } ... ); } }
Named route로 이동하기 위해, of메서드를 사용하여 Navigator 위젯의 BuildContext를 지정함.
- Route 의 이름은 pushNamed 함수를 통해 전달하여 특정한 route로 이동함
1
Navigator.of(context).pushNamed('/a');
Navigator의 push 메서드로 주어진 route를 가장 가까운 context를 가진 네비게이터에 추가하고 이동할 수 있음.
- 아래는 MaterialPageRoute 위젯이 플랫폼에 적함한 전환효과와 함께 전체 스크린을 modal route로 전환시킴.
- 필수 매개변수로 WidgetBuilder를 넣어줘야함
1 2
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => UsualNavscreen()));
탭 네비게이션
Flutter는 drawer 및 탭 네비게이션을 위한 여러 특수 위젯을 제공함
- TabController: TabBar와 TabBarView 사이에서 탭 선택을 조정
- lengths : 전체 탭 개수, vsync : 프레임이 상태 변화를 할때마다 알려주기 위한 TickerProvider
- TabBar: 탭의 수평 열을 보여줌
- Tab: Material 디자인으로 TabBar 탭을 생성
- TabBarView: 현재 선택된 탭에 맞는 위젯을 보여줌.
1 2 3 4 5 6 7 8 9 10
// Flutter TabController controller=TabController(length: 2, vsync: this); TabBar( tabs: <Tab>[ Tab(icon: Icon(Icons.person),), Tab(icon: Icon(Icons.email),), ], controller: controller, ),
- TabController: TabBar와 TabBarView 사이에서 탭 선택을 조정
TickerProvider : Ticker 객체를 제공할 수 있는 클래스에 의해 구현된 인터페이스
Ticker : 프레임이 트리거 될때마다 알림을 받아야하는 모든 객체에서 사용할 수 있음
보통 AnimationController를 통해 간접적으로 사용함
AnimationController는 Ticker를 얻기위해 TickerProvider가 필요함
State에서 AnimationController를 만들고 있다면, TickerProviderStateMixin 또는 SingleTickerProviderStateMixin 클래스를 사용하여 적절한 TickerProvide를 얻음
아래 Scaffold 위젯은 새로운 TabBar 위젯을 감싸고 2개의 탭을 생성
- TabBarView는 body 인자로 전달됨.
- TabBar 위젯의 탭에 해당하는 모든 화면은 같은 TabController를 가지고 있는 TabBarView 위젯의 자식들임
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
class _NavigationHomePageState extends State<NavigationHomePage> with SingleTickerProviderStateMixin { TabController controller=TabController(length: 2, vsync: this); @override Widget build(BuildContext context) { return Scaffold( bottomNavigationBar: Material ( child: TabBar( tabs: <Tab> [ Tab(icon: Icon(Icons.person),) Tab(icon: Icon(Icons.email),), ], controller: controller, ), color: Colors.blue, ), body: TabBarView( children: <Widget> [ home.homeScreen(), tabScreen.tabScreen() ], controller: controller, ) ); } }
Drawer 네비게이션
- Drawer 위젯을 Scaffold와 조합하여 material 디자인의 drawer를 만들 수 있음
- Drawer을 Scaffold 위젯으로 감싸야함
- Drawer 위젯은 Scaffold 의 왼쪽/오른쪽 모서리에서 슬라이트 형태로 등장하여 앱의 네비게이션 링크를 보여줌
- Button이나 Text 또는 여러 아이템 목록을 Drawer 위젯의 자식으로 보여줄 수 있음
- 아래 예제는 ListTile 을 자식으로 보여줌
- Scaffold 위젯은 Drawer을 사용할 수 있을 때 Drawer로 연결되는 IconButton을 띄어주는 AppBar 위젯을 포함함.
- 왼쪽 위에 버튼 눌러서 Drawer 가능하도록
- Scaffold를 사용하면 edge-swipe 동작을 했을때 자동으로 Drawer가 나타남
- 왼쪽에서 당겼을때 drawer가 나오도록
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: ListTile(
leading: Icon(Icons.change_history),
title: Text('Screen2'),
onTap: () {
Navigator.of(context).pushNamed('/b');
},
),
elevation: 20.0,
),
appBar: AppBar(
title: Text('Home'),
),
body: Container(),
);
}
제스처 감지와 터치 이벤트 다루기
- Flutter 제스처는 2개의 층이 있음
- 첫번째 층 : 포인터의 위치와 움지기임을 표현하는 원시 포인터 이벤트(터치, 마우스, 스타일러스 동작)
- 두번째 층 : 하나 이상의 포인터 움직임으로 만들어진 동작을 표현하는 제스처
Click/Press 리스너
ReactNative 에서는 Touchable 컴포넌트를 사용함
Flutter에서는 onPress : field 를 가진 버튼이나 터치 가능한 위젯을 사용함
- 또는 어떤 위젯이건 GestureDetector로 감싸서 제스처 감지를 추가할 수도 있음
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
// Flutter GestureDetector( child: Scaffold( appBar: AppBar( title: Text('Gestures'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Tap, Long Press, Swipe Horizontally or Vertically '), ], ) ), ), onTap: () { print('Tapped'); }, onLongPress: () { print('Long Pressed'); }, onVerticalDragEnd: (DragEndDetails value) { print('Swiped Vertically'); }, onHorizontalDragEnd: (DragEndDetails value) { print('Swiped Horizontally'); }, );
HTTP 네트워크 요청 만들기
- Flutter에서는 http 패키지를 사용함. 아래처럼 의존성 추가
1
2
3
4
dependencies:
flutter:
sdk: flutter
http: <latest_version>
HTTP 클라이언트 지원에 dart:io 를 사용함.
- dart:io 를 import하여 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13
import 'dart:io'; ... final url = Uri.https('httpbin.org', 'ip'); final httpClient = HttpClient(); _getIPAddress() async { var request = await httpClient.getUrl(url); var response = await request.close(); var responseBody = await response.transform(utf8.decoder).join(); String ip = jsonDecode(responseBody)['origin']; setState(() { _ipAddress = ip; }); }
Form input
TextField 위젯 사용
- TextEditingController클래스를 사용하여 TextField 위젯을 관리함
- 위 컨트롤러는 텍스트 필드가 수정될때마다 리스너에게 알림
- 리스너는 사용자가 필드에 입력한 내용을 알기 위해 text와 selection 속성을 읽음
- text 속성을 활용하여 TextField 에 있는 텍스트에 접근가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Flutter
final TextEditingController _controller = TextEditingController();
...
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Type something', labelText: 'Text Field '
),
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
showDialog(
context: context,
child: AlertDialog(
title: Text('Alert'),
content: Text('You typed ${_controller.text}'),
),
);
},
),
)
Form
- Form 위젯의 자식으로 TextFormField 위젯들을 가져서 구현가능
- TextFormField 위젯에는 form이 저장될 때 실행되는 콜백을 받는 onSaved라는 매개변수가 있음
- FormState 객체는 Form 하위에 있는 각 FormField 를 저장하고, 초기화하고, validation하기 위해 사용함
- Form의 부모 context와 함께 Form.of를 사용하거나, Form 생성자에 GlobalKey를 넘기고 GlobalKey.currentState를 호출하여 FormState를 가져올 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final formKey = GlobalKey<FormState>();
...
Form(
key:formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) => !value.contains('@') ? 'Not a valid email.' : null,
onSaved: (val) => _email = val,
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
),
RaisedButton(
onPressed: _submit,
child: Text('Login'),
),
],
),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
showDialog(
context: context,
child: AlertDialog(
title: Text('Alert'),
content: Text('Email: $_email, password: $_password'),
)
);
}
}
Platform-specific 코드
1
2
3
4
5
6
7
8
9
10
// Flutter
if (Theme.of(context).platform == TargetPlatform.iOS) {
return 'iOS';
} else if (Theme.of(context).platform == TargetPlatform.android) {
return 'android';
} else if (Theme.of(context).platform == TargetPlatform.fuchsia) {
return 'fuchsia';
} else {
return 'not recognised ';
}
## 디버깅
- DevTools를 사용
- 프로파일링, 힙검사, 위젯트리 조사, 로깅 진단, 디버깅, 실행된 코드라인 관찰, 메모리 누수 및 메모리 조각화 디버깅 지원
인앱 개발자 메뉴에 접근
- IDE를 사용중이면, IDE 도구를 사용할 수 있음
- flutter run을 사용했다면, 터미널 창에서 h를 눌러서 접근가능
- 또는 다양한 터미널 단축키등이 있음 : 링크
애니메이션
- Animation 클래스
- 현재 값과 상태(완료 또는 해제)를 알고 있는 추상 클래스
- AnimationController 클래스
- 애니메이션을 앞뒤로 재생하거나, 중지, 특정 값으로 설정 하는 등의 모션을 커스터마이징 가능
간단한 fade-in 애니메이션
AnimationController 객체를 만들고, 기간을 지정
기본적으로 AnimationController는 특정 기간 동안 0.0 ~ 1.0 범위에서 값이 선형적으로 증가함
일반적으로 초당 60 FPS로, 기기에서 새로운 프레임을 보여줄 준비가 됐을 때마다 새로운 값을 생성함
AnimationController를 정의할 때, vsync 객체를 넘겨줘야함
그래야 화면이 꺼져있을때 애니메이션에 불필요한 리소스를 소비하지 않음
클래스 정의에 TickerProviderStateMixin을 추가하여 상태 저장객체를 vsync로 사용할 수 있음
AnimationController 에는 생성자에 vsync 인수를 사용하여 만들어진 TickerProvider가 필요함
Tween은 시작값과 끝 값 사이의 보간 또는 입력 범위에서 출력 범위까지의 매핑을 표현함
- 애니메이션과 함께 Tween 객체를 사용하기 위해, Tween 객체의 animate 메서드를 호출한 뒤 수정하고 싶은 Animation 객체로 넘김
아래 예제에서는 FadeTransition 위젯을 사용하고, opacity 속성을 animation 객체에 매핑시킴
- 애니메이션을 시작하기 위해 controller.forward()를 사용함
- fling() 이나 repeat()를 사용하여 다른 동작도 가능
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 31 32 33 34 35 36 37 38 39 40 41
// Flutter import 'package:flutter/material.dart'; void main() { runApp(Center(child: LogoFade())); } class LogoFade extends StatefulWidget { _LogoFadeState createState() => _LogoFadeState(); } class _LogoFadeState extends State<LogoFade> with TickerProviderStateMixin { Animation animation; AnimationController controller; initState() { super.initState(); controller = AnimationController( duration: const Duration(milliseconds: 3000), vsync: this); final CurvedAnimation curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); animation = Tween(begin: 0.0, end: 1.0).animate(curve); controller.forward(); } Widget build(BuildContext context) { return FadeTransition( opacity: animation, child: Container( height: 300.0, width: 300.0, child: FlutterLogo(), ), ); } dispose() { controller.dispose(); super.dispose(); } }
카드에 스와이프 애니메이션을 추가하는 법
- Dismissble 위젯을 사용하여 자식 위젯을 감쌈
1
2
3
4
5
6
7
8
9
child: Dismissible(
key: key,
onDismissed: (DismissDirection dir) {
cards.removeLast();
},
child: Container(
...
),
),