Flutter란?
Flutter는 Google에서 개발한 오픈소스 어플리케이션 프레임워크이다. Flutter는 안드로이드, iOS, 리눅스, 맥, 윈도우를 위한 크로스 플랫폼 어플리케이션을 개발하는 데 사용될 수 있다. 현재까지 최신 버전은 May 19, 2021에 릴리즈된 Flutter 2.2이다. 1 2
앞으로 소개할 입문 글들은 모두 flutter.dev에 기반하여 작성된다. 3
widget
widget이란?
flutter에서 widget은 중심적인 구성요소로서, UI의 각 부분에 대한 immutable한 description이다. widget은 반복적으로 사용될 수 있어서, tree에 몇 번이라도 포함될 수 있다. widget은 기본적으로 immutable하기 때문에, state를 변경하고 싶다면 StatefulWidget을 사용하는 방법을 고려할 수 있다. 4
flutter widget은 React와 비슷하게 동작한다. widget은 build() 메소드를 통해서 그려지는데, 만약 트리에 새로운 widget이 삽입되면 build 메소드는 framework에 의해 call되어 build는 각 위치에 있는 widget을 low-level widget으로 describe한다. 최종적으로는 widget의 형태를 계산하는 RenderObject class를 나타내는 위젯으로 끝날 때까지 widget을 차례로 계산하게 된다.
Hello world
"Hello, world!"는 flutter에서 다음과 같이 작성할 수 있다.
import 'package:flutter/material.dart';
void main() {
runApp(
const Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
다음과 같이 출력되는 것을 볼 수 있다.
여기서 runApp() 함수는 widget을 받아서 트리를 만든다. 이 트리는 Center라는 widget과 Center의 child node인 Text widget으로 구성되어 있다. flutter는 root widget이 화면을 꽉 채우도록 만들기 때문에 여기서는 Center widget이 화면을 꽉 채워 Center에 'Hello, World!'라는 문자열 리터럴이 중앙에 놓이게 된다.
기본 위젯들
기본적으로 사용하는 위젯들은 다음과 같다 :
Text
어플리케이션에 스타일이 적용된 텍스트를 입력할 수 있도록 한다.
Row, Column
이 위젯들은 horizontal하거나 vertical한 방향으로 flexible한 레이아웃을 작성할 수 있도록 도와준다.
Stack
linear지 않게 stack처럼 widget을 놓을 수 있게 해 준다. 이후 Positioned 위젯으로 relative하게 top, right, bottom, left 중 어디에 놓을 지 결정할 수 있다. Stack은 absolute positioning 레이아웃 모델에 기반한다.
Container
직사각형의 엘리먼트를 만들 수 있도록 해 준다. 이 container는 BoxDecoration으로 꾸밀 수 있다. Container는 또한 margin, padding, constraint를 가질 수 있고 matrix를 이용하여 3-dimenstion하게 변화할 수도 있다.
simple widget들을 다음과 같은 예시를 만들 수 있다 :
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
const MyAppBar({required this.title, Key? key}) : super(key: key);
final Widget title;
@override
Widget build(BuildContext context) {
return Container(
height: 56.0,
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(color: Colors.blue[500]),
child: Row(
children: [
const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation Menu',
onPressed: null,
),
Expanded(
child: title,
),
const IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
const MyScaffold({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: [
MyAppBar(
title: Text(
'MyTitle',
style: Theme.of(context).primaryTextTheme.headline6,
),
),
const Expanded(
child: Center(
child: Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(
const MaterialApp(
title: 'MyApp',
home: SafeArea(
child: MyScaffold(),
),
),
);
}
이 코드의 실행 결과는 다음과 같다.
main 함수부터 살펴보자. main 함수는 runApp 메서드로 MaterialApp이라는 객체의 위젯을 가지고 트리를 만든다. MaterialApp 객체의 프로퍼티는 2개가 있다. title 프로퍼티는 운영체제 task switcher에 의해 사용되는 이름을 정한다. home 프로퍼티는 SafeArea 클래스를 값으로 갖는다. SafeArea 클래스는 말 그대로 안전한 공간을 만들어주는 클래스이다. SafeArea 클래스 안에 들어있는 위젯은 충분한 padding을 갖게 되어 다른 요소들이 침범하지 않도록 해 준다. SafeArea는 child로 MyScaffold 클래스를 가진다. 5
MyScaffold 클래스는 build Widget을 오버라이딩하여 Material 클래스를 리턴한다. Material 클래스는 material design이라고, Android에서 흔하게 사용되는 그림자가 드리우는 형태의 디자인을 가진다. Material 클래스는 child로 MyAppBar 클래스와 Center 클래스를 vertical하게 가진다. 즉, 맨 위에 MyAppBar 클래스가 그려지고 그 아래에 expanded된 형태의 Center 클래스가 그려지는 것이다. MyAppBar 클래스는 final로 title을 선언한다. final은 런타임 안에서 설정된 값이 변경될 수 없도록 한다. 6
MyAppBar 클래스는 Build를 오버라이딩하는데, Container 클래스를 리턴한다. Container 클래스는 색과 위치, 크기를 쉽게 결정할 수 있도록 하는 편리한 위젯이다. Container 클래스는 프로퍼티로 child를 가질 수 있다. 여기서는 horizontal하게 세 개의 객체를 child로 가진다. IconButton, Expanded, IconButton이다. IconButton 클래스는 아이콘 버튼의 material 디자인을 제공한다. 7 8
Material Component 사용하기
MatrialApp 클래스를 이용하여 Material component를 가지고 테스트 앱을 만들어 보자.
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
title : 'MyFlutterApp',
home : MyHome(),
),
);
}
class MyHome extends StatelessWidget {
const MyHome({Key?key}) : super(key:key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar : AppBar(
leading : const IconButton(
icon:Icon(Icons.menu),
tooltip:'Navigation menu',
onPressed : null,
),
title: const Text('Example title'),
actions : const [
IconButton(
icon : Icon(Icons.search),
tooltip : 'Search',
onPressed : null,
),
],
),
body: const Center(
child : Text('Hello, world!'),
),
floatingActionButton:const FloatingActionButton(
tooltip: 'Add',
child: Icon(Icons.add),
onPressed:null,
),
);
}
}
이 코드의 실행 결과는 다음과 같다 :
main 함수에서 MaterialApp 클래스를 runApp 메소드 안에서 실행한다.
MaterialApp class
- material 디자인을 위해 사용하는 어플리케이션이다.
- 여러 widget을 감쌀 경우 child widget을 material design으로 만들어 준다.
- Navigator(화면이 부드럽게 이동할 수 있도록 함)를 포함한 유용한 widget들을 app의 root에 만든다.
Scaffold class
- 기본적인 material design을 implement한다.
- available space로 확장하여 전체 화면을 차지한다.
- appBar, backgroundColor, body를 포함한 여러 프로퍼티를 가진다.
위 앱의 구조는 MaterialApp 클래스 안에 home 프로퍼티로 Scaffold 클래스를 리턴하는 MyHome 클래스를 가지는 것이다. MyHome 클래스 내부의 Scaffold 클래스에서는 appBar, body, floatingActionButton 클래스를 프로퍼티로 가진다.
간단한 버튼 만들기
import 'package:flutter/material.dart';
class MyButton extends StatelessWidget {
const MyButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 50.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: const Center(
child: Text('Engage'),
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: MyButton(),
),
),
),
);
}
이 코드의 실행 결과는 아래와 같다.
이 코드의 핵심은 GestureDetector 클래스이다. MyButton 클래스는 GestureDetector클래스를 반환한다.
GestureDetector 클래스 9
- 움직임을 감지하는 widget이다.
- child가 있다면 child의 크기에 맞게 크기가 정해진다. child가 없다면, 부모 크기에 맞게 크기가 정해진다.
- 버튼 등을 만들때 사용될 수 있다. 이 위젯의 child에 container를 가지면 버튼 박스가 만들어진다.
- 프로퍼티는 behavior, child, onTap 등이 있다.
- 많은 위젯들이 GestureDetector를 사용하여 다른 위젯의 callback을 제공한다.
카운터 버튼 만들기
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
const SizedBox(width: 16),
Text('Count: $_counter'),
],
);
}
}
void main() {
runApp(const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
))));
}
이 코드의 실행 결과는 다음과 같다.
StatefulWidget과 State가 다른 객체인 이유는 Flutter에서 두 타입의 객체가 다른 life cycle을 가지기 때문이다. Widget은 현재 상태를 보여주고 사라지는 일시적 객체지만, State 객체는 정보를 저장해야 하기 때문에, build의 call 사이에서 사라지지 않고 존재한다.
위의 예제에서는 _CounterState 클래스 안에서 바로 사용자의 클릭을 받고 클릭으로 onPressed 이벤트로 _increment 함수를 콜하는 것까지 하나의 클래스 안에서 수행된다. 그러나 프로그램이 복잡해질 경우 각 계층에서는 하나의 일만 할 수 있도록 하는 것이 권장된다. 따라서 Flutter에서는 일반적으로 변경 콜백은 upward로, 현재 state의 변화는 downward stateless widget을 통해 수행된다. 이를 반영하면 다음과 같다 :
import 'package:flutter/material.dart';
class CounterDisplay extends StatelessWidget {
const CounterDisplay({required this.count, Key? key}) : super(key: key);
final int count;
@override
Widget build(BuildContext context) {
return Text('Count : $count');
}
}
class CounterIncrementor extends StatelessWidget {
const CounterIncrementor({required this.onPressed, Key? key}) : super(key: key);
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: const Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CounterIncrementor(onPressed: _increment),
const SizedBox(width: 16),
CounterDisplay(count: _counter),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
위의 코드와 다르게 더 복잡해진 것을 알 수 있다. 본래의 _CounterState 클래스에서 직접 버튼을 만들지 않고 children widget으로 CounterIncrementor을 만드는 것을 알 수 있다. _counter값을 그리는 것도 CounterDisplay라는 StatelessWidget을 만들어 사용한다.
여기서 Counter 클래스에서 오버라이딩하는 createState 메소드는 다음과 같다 :
StatefulWiget 클래스는 다음과 같다 :
참고문헌
- https://medium.com/flutter/announcing-flutter-2-2-at-google-i-o-2021-92f0fcbd7ef9 [본문으로]
- https://en.wikipedia.org/wiki/Flutter_(software) [본문으로]
- https://flutter.dev/ [본문으로]
- https://api.flutter.dev/flutter/widgets/Widget-class.html [본문으로]
- https://api.flutter.dev/flutter/widgets/SafeArea-class.html [본문으로]
- https://dart.dev/guides/language/language-tour#final-and-const [본문으로]
- https://api.flutter.dev/flutter/widgets/Container-class.html [본문으로]
- https://api.flutter.dev/flutter/material/IconButton-class.html [본문으로]
- https://api.flutter.dev/flutter/widgets/GestureDetector-class.html [본문으로]
- https://api.flutter.dev/flutter/widgets/StatefulWidget/createState.html [본문으로]
- https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html [본문으로]
'CS > Dart·Flutter' 카테고리의 다른 글
[Flutter] flutter 입문 2. AppBar class (0) | 2021.09.15 |
---|