2일차 과정에서는 플러터 개발을 위한 환경 설정과 기본적인 위젯들을 배우고 사용해 보았다. 아래는 학습한 내용들의 키워드이다.
학습한 내용
- 타입 추론
- SDK와 IDE
- Flutter와 VScode 환경 설정
- Flutter의 위젯(Widget)
- 기본 위젯 (MaterialApp, Scaffold, SafeArea, Center, Text, Icon)
Fixing issues with Flutter on Windows
Flutter 설치 후 flutter doctor -v를 실행했을 때 아래와 같은 이슈가 발생하였다.
Windows Version (Unable to confirm if installed Windows version is 10 or greater)
아래와 같은 커맨드를 입력해 Switching channels를 시도 했더니 이슈가 해결되었다.
flutter channel dev
flutter upgrade
flutter config --enable-windows-desktop
- Generic 이란?
- Flutter의 렌더링 원리
- 다양한 Text 위젯 사용 연습(개발)
- CircleAvatar 위젯 사용 연습(개발)
제네릭(Generic)이란 데이터의 타입을 일반화한다(generalize)는 것을 의미한다.
예를 들어 리스트를 정의할 때 List<>
에서 <...>
괄호를 사용하여 안에 타입을 지정하는 것도 제네릭이라고 한다.
var firstNames = <String>["Kim", "Lee", "Park"]; // List
var names = <String>{"jimin", "minsu", "hana"}; //Set
var ages = <String, int> { //Map
"jimin" : 12,
"minsu" : 15,
"hana" : 18
};
정리하면 List, Set, Map과 같은 컬렉션에서 사용하는 것처럼 <> 부분에 타입 매개변수를 지정한다. 이렇게 <>
에 타입 매개변수를 선언하는 것을 매개변수화 타입을 정의한다고 한다.
추가로 Dart의 제네릭 타입들은 Java와 다르게 런타임 동안 타입의 정보가 없어지지 않는다.
클래스 생성 시 생성자에서 사용하거나 함수 호출 수 인자 값을 전달 하기 위해 매개변수를 사용하는데 타입 매개변수는 전달하는 것이 인자 값이 아니고 타입이다.
abstract class List<E> implements EfficientLengthIterable<E> {
...
}
다트에서 List 클래스가 위와 같이 선언되어 있기 때문에 리스트를 생성할 때 타입 매개 변수를 지정할 수 있다.
List<String> students = List();
이렇게 제네릭을 사용하면 동일한 동작을 해야 하는 코드를 데이터 타입별로 따로 코드를 작성할 필요가 없게되어 코드를 줄일 수 있다.
또한 extends
를 사용하면 매개변수화 타입을 제한할 수도 있다.
class A {
...
}
class B extends A {
...
}
class C<T extends A> {
...
}
void main() {
var a = C<A>();
var b = C<B>();
var c = C();
}
위의 예시에서 C 클래스가 타입 매개변수로 T extends A
를 선언했다. 따라서 C클래스의 타입 매개변수는 A 클래스와 A클래스의 자식 클래스만 될 수 있다.
제네릭은 클래스 말고 메서드에도 사용 가능하다. 메서드에서는 리턴 타입, 매개변수를 제네릭으로 설정할 수 있다. 제네릭 메서드 사용 예시는 아래와 같다.
class Person {
T getName<T>(T name) {
return name;
}
}
void main() {
var person = Person();
print(person.getName<String>("kwanwoo"));
}
플러터는 모든 것이 위젯으로 구성되어 있다. 시각적인 부분을 담당하는 Dart 코드는 Skia 사용을 위한 네이티브 코드로 컴파일 된다. 플러터는 엔진에 Skia 복사본을 포함하고 있어서 안드로이드 버전이 낮아도 앱의 성능을 최신의 성능을 낼 수 있게 해준다. IOS, Windows, macOS에도 똑같이 동작한다.
즉, 기존의 크로스 플랫폼 앱은 네이티브 코드와 상호작용을 통해 드로잉을 수행하지만 플러터는 자신의 UI 코드를 사용해 드로잉을 수행한다. 그렇기 때문에 네이티브 코드를 거치거나 상호작용을 하지 않기 때문에 성능이 좋다.
플러터는 아래와 같은 두 가지 종류의 쓰레드를 통해 렌더링을 한다.
- UI Thread
UI 쓰레드는 아래 그림에서 1 부터 5까지 단계를 수행한다. 이 때, 어플리케이션과 플러터 코드를 포함한 다트코드가 다트 VM에서 살행된다. 그리고 레어이 트리 및 드로잉 설명을 생성하기 위한Widget 트리
,Element 트리
,RenderObject 트리
를 생성한다.Layer 트리
는 드로잉 설명이 저장된다.- GPU Thread
GPU 쓰레드는 6 단계를 수행한다. 플러터 엔진의 Skia와 관련된 그래픽 코드를 수행하며,Layer 트리
를 얻기위해 GPU와 상호작용하고,Layer 트리
를 구성하고, 트리를 스크린에 표현한다.
//예시 코드
Container(
color: Colors.blue,
child: Row(
children: [
Image.network('https://www.example.com/1.png'),
const Text('A'),
],
),
);
플러터가 위의 코드를 렌더링 할 때, 현재 앱 상태를 기반으로 위젯 하위트리를 리턴하는 build()
메서드를 실행한다. 이는 위의 파이프라인 간략화 그림에서 Build 단계에 해당한다.
Container
는 color
와 child
속성을 가지고 있고 Container
의 소스코드는 아래와 탁이 null
이 아니면 색상을 표현하는 ColoredBox
가 삽입된다.
if (color != null)
current = ColoredBox(color: color!, child: current);
이와 같이 Image
와 Text
위젯은 Build 과정에서 자식 위젯으로 RawImage
와 RichText
를 삽입하게 된다. 결국 위젯 계층은 아래 그림과 같이 구성된다.
다음으로 Build 과정을 통해 플러터는 표현된 모든 위젯에 대해 각각의 엘리먼트를 가지도록 Element 트리에 변환한다.
각 엘리먼트는 트리 계층에서의 주어진 위치의 위젯의 인스턴스이다. 엘리먼트는 두 가지 타입이 있는데 아래와 같다.
엘리먼트 타입
- ComponentElement : 다른 엘리먼트의 호스트
- RenderObjectElement : 레이아웃 또는 페인트에 관련된 엘리먼트
엘리먼트는 트리에서 위젯의 위치에 관여하는 BuildContext
를 통해 참조될 수 있는데 이는 build()
메소드의 파라미터로 제공된다.
만약 위와 같은 트리 구조에서 Text의 글자가 바뀌는 등의 Widget 트리의 변화가 생긴다면, 위젯은 원칙적으로 불변이기 때문에 새로운 위젯 세트가 반환되어야 한다.
플러터의 Element 트리는 각각 프레임에 영속적이기 때문에 하나의 엘리먼트 트리만을 사용하는 것처럼 보이며, Element 트리에서 재설정이 필요한 부분만 리빌드 하기 때문에 리소스의 낭비가 아니다.
마지막으로 플러터는 레이아웃과 페인팅을 정의하기 위한 추상 모델로 RenderObject를 사용하고, 이는 Render 트리의 모든 노드의 베이스 클래스가 된다. Build 과정 동안, 플러터는 element 트리의 RenderObjectElement에 대해 RenderObject를 구현하는 오브젝트를 만들거나 업데이트 한다.
대부분의 위젯은 2D 데카르트 좌표계, 고정 크기의 RenderBox를 구현함으로 렌더링 된다. RenderBox는 최대, 최소 높이와 너비를 렌더링하는데 사용한다.
플러터는 레이아웃을 수행하기 위해 Render 트리의 처음부터 아래로 깊이 우선 탐색(DFS)을 수행하며, 크기 제약을 자식 노드에게 전해준다. 자식은 부모가 준 제약 조건을 따라야 하며, 받은 제약 조건 내에서 자식은 크기를 부모에게 전달한다. 이러한 과정을 통해 모든 Object들의 크기가 결정되고 Paint 과정을 수행할 준비가 된다.
Render 트리의 총 출력을 표현하는 RenderView가 트리의 루트에 있고, 플랫폼이 새 프레임을 요구하면 RenderView 객체의 compositeFrame()
메서드가 호출된다. 이 메서드는 SceneBuilder를 만들고 이 Builder는 scene을 업데이트한다.
scene 업데이트가 완료된 다음 RenderView 객체는 조합된 scene을 Window.render()
메소드로 보내 GPU에게 렌더링을 지시한다. 이러한 전체적인 과정을 통해 플러터는 렌더링을 수행한다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(
'''
이듬해 질 녘 꽃 피는 봄 한여름 밤의 꿈
가을 타 겨울 내릴 눈 일 년 네 번 또다시 봄
정들었던 내 젊은 날 이제는 안녕
아름답던 우리의 봄 여름 가을 겨울
(Four seasons with no reason)\n
비 갠 뒤에 비애 대신 a happy end
비스듬히 씩 비웃듯 칠색 무늬의 무지개
철없이 철 지나 철들지 못해 (still)
철부지에 철 그른지 오래\n
Marchin' 비발디, 차이코프스키
오늘의 사계를 맞이해 (boy)
마침내, 마치 넷이 못내\n
저 하늘만 바라보고서
사계절 잘 지내고 있어, goodbye
떠난 사람 또 나타난 사람
머리 위 저세상, 난 떠나 영감의 amazon\n
지난 밤의 트라우마 다 묻고
목숨 바쳐 달려올 새 출발 하는 왕복선
변할래 전보다는 더욱더
좋은 사람 더욱더, 더 나은 사람 더욱더\n
아침 이슬을 맞고 (내 안에)
내 안에 분노 과거에 묻고
For life, do it away, away, away
'''
),
),
),
);
}
}
Multi-line strings '''
을 사용하여 텍스트 위젯의 파라미터로 입력하면 여러 줄의 문자열을 한 번에 입력하고 출력할 수 있다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(
textAlign: TextAlign.center, // 가운데 정렬
style:TextStyle(
fontSize: 16, // 폰트 사이즈
color: Colors.blue, // 글씨 색상
fontWeight: FontWeight.bold, //볼드체
),
'''
이듬해 질 녘 꽃 피는 봄 한여름 밤의 꿈
가을 타 겨울 내릴 눈 일 년 네 번 또다시 봄
정들었던 내 젊은 날 이제는 안녕
아름답던 우리의 봄 여름 가을 겨울
(Four seasons with no reason)\n
비 갠 뒤에 비애 대신 a happy end
비스듬히 씩 비웃듯 칠색 무늬의 무지개
철없이 철 지나 철들지 못해 (still)
철부지에 철 그른지 오래\n
Marchin' 비발디, 차이코프스키
오늘의 사계를 맞이해 (boy)
마침내, 마치 넷이 못내\n
저 하늘만 바라보고서
사계절 잘 지내고 있어, goodbye
떠난 사람 또 나타난 사람
머리 위 저세상, 난 떠나 영감의 amazon\n
지난 밤의 트라우마 다 묻고
목숨 바쳐 달려올 새 출발 하는 왕복선
변할래 전보다는 더욱더
좋은 사람 더욱더, 더 나은 사람 더욱더\n
아침 이슬을 맞고 (내 안에)
내 안에 분노 과거에 묻고
For life, do it away, away, away'''
),
),
),
);
}
}
textAlign
속성을 TextAlign.center
로 설정해 가운데 정렬을 수행했고, fontSize
속성을 16으로 설정했다. 글씨 색상은 color
속성을 Colors.blue
로 설정해 파란색으로 바꿔주었다. fontWeight
속성을 fontWeight.bold
로 설정해 볼드체를 적용했다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: RichText(
textAlign: TextAlign.center, //가운데 정렬
text: TextSpan(
style: TextStyle(
fontSize: 16, //글씨 크기
fontWeight: FontWeight.bold //볼드체
),
children: <TextSpan>[
TextSpan(
style: TextStyle(color:Colors.blue), //파란색
text:"이듬해 질 녘 꽃 피는 봄 한여름 밤의 꿈\n가을 타 겨울 내릴 눈 일 년 네 번 또다시 봄\n정들었던 내 젊은 날 이제는 안녕\n아름답던 우리의 봄 여름 가을 겨울\n(Four seasons with no reason)\n\n"
),
TextSpan(
style: TextStyle(color:Colors.red), //빨간색
text:"비 갠 뒤에 비애 대신 a happy end\n비스듬히 씩 비웃듯 칠색 무늬의 무지개\n철없이 철 지나 철들지 못해 (still)\n철부지에 철 그른지 오래\n\n"
),
TextSpan(
style: TextStyle(color:Colors.green), //초록색
text:"Marchin' 비발디, 차이코프스키\n오늘의 사계를 맞이해 (boy)\n마침내, 마치 넷이 못내\n\n"
),
TextSpan(
style: TextStyle(color:Colors.grey), //회색
text:"저 하늘만 바라보고서\n사계절 잘 지내고 있어, goodbye\n떠난 사람 또 나타난 사람\n머리 위 저세상, 난 떠나 영감의 amazon\n\n"
),
TextSpan(
style: TextStyle(color:Colors.yellow), //노란색
text:"지난 밤의 트라우마 다 묻고\n목숨 바쳐 달려올 새 출발 하는 왕복선\n변할래 전보다는 더욱더\n좋은 사람 더욱더, 더 나은 사람 더욱더\n\n"
),
TextSpan(
style: TextStyle(color:Colors.indigo), //인디고색
text:"아침 이슬을 맞고 (내 안에)\n내 안에 분노 과거에 묻고\nFor life, do it away, away, away"
),
]),
),
),
),
);
}
}
RichText
위젯 안에 TextSpan
위젯을 사용하여 각각의 문단을 입력하고 서로 다른 색상을 설정해 주었다. 속성은 위에서 사용한 Text
위젯과 같다. 전체 문단에 적용되는 가운데 정렬은 RichText
위젯의 속성으로 설정했고, 글씨 크기와 볼드체는 모든 문단을 묶는 상위 TextSpan
위젯에 설정했다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: CircleAvatar(
radius: 100.0, // 크기 설정
backgroundColor: Colors.indigoAccent, //색상 설정
child: Icon(
//그림자
shadows: <Shadow>[Shadow(color: Colors.black, blurRadius: 10.0, offset: Offset(5, 5))],
color: Colors.white, //아이콘 색상
size:100, // 아이콘 크기
Icons.ac_unit_rounded,
),
),
),
),
);
}
}
CircleAvatar
위젯의 크기를 radius
속성을 사용해 100.0으로 설정하고 배경 색을 backgroundColor
속성을 사용해 Colors.indigoAccent
로 설정했다. 아래에 child로 Icon
위젯을 사용해 Icons.ac_unit_rounded
아이콘을 삽입했다. 아이콘의 색은 흰색으로 설정하고 크기는 100으로 설정했다. 마지막으로 아이콘의 그림자를 shadow
속성을 사용해 넣어주었다.
스나이퍼팩토리 플러터과정 2일차를 진행했다. 오늘은 본격적으로 플러터를 사용하기 위한 환경 설정을 진행하고 간단한 위젯들을 사용해 보았다. 아직까지는 예전에 사용해 본 경험이 있는 위젯들이라 크게 어렵진 않았다. 오히려 이번에 처음 플러터의 렌더링 방식에 대해 공부했는데 내용이 생각보다 어려워서 이해하는데 조금 오래 걸렸다 ㅠㅠ 이렇게 2일차는 마무리 ㅎㅎ