✅ 기본 문제 - HelloLayout
✅ 심화 문제 - MySlackAvatar
오늘의 기본 문제는 이런 레이아웃을 만들어보는 문제였다. 화면 사이즈에 영향을 받는 레이아웃이었는데, 위에 4등분 되어있는 정사각형은 항상 사이즈를 유지하고 있다.
이를 구현하려면 디스플레이의 가로 길이를 알아야 한다고 생각했다. 그러기 위해 사용한 MediaQuery!
MediaQueryData는 미디어에 대한 정보다. 현재 미디어의 창에 대한 정보를 담고 있다. 주어진 BuildContext에 대한 미디어 쿼리 데이터를 받기 위해서 사용되는 것이 바로 MediaQuery.of 함수다.
만약에 범위 내에 MediaQuery가 없다면, MediaQuery.of 메서드는 예외를 throw 한다. MediaQuery.maybeOf 메서드로 예외 대신 null
을 받는 방법도 있다. MediaQuery에는 굉장히 다양한 메서드가 있는데, maybe~가 붙는 메서드들은 모두 일치하는 데이터가 없다면 예외 대신 null
을 리턴하는 메서드들이다.
MediaQuery는 WidgetsApp 및 MaterialApp에서 사용된다. MediaQuery의 특정 메서드를 사용해서 현재의 미디어를 쿼리하면, 쿼리하는 속성이 변경될 때마다 위젯이 자동으로 다시 빌드된다.
반면에! MediaQuery.of를 사용해서 쿼리하면, MediaQueryData의 필드가 변경될 때마다 위젯이 자동으로 다시 빌드된다. 때문에 MediaQueryData의 특정 필드만 사용하는 경우 해당 메서드만 사용하는 것이 좋다.
class HelloLayout extends StatelessWidget {
const HelloLayout({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.amber,
title: const Text(
'I can layout this',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
body: SafeArea(
child: Column(
children: [
Container(
height: MediaQuery.of(context).size.width,
color: Colors.black,
child: Container(),
),
Expanded(
child: Column(
children: [
Expanded(flex: 2, child: Container(color: Colors.purple)),
Expanded(flex: 1, child: Container(color: Colors.grey)),
],
),
),
],
),
),
);
}
}
나는 이렇게 body에 Column 위젯을 사용하여 크게 두 파트로 나눴다. 첫 번째 자식 Container는 고정된 가로, 세로 길이를 가지고 있다. 1:1 비율을 원했기 때문에, MediaQuery.of 메서드를 사용하여 미디어의 가로 길이를 구하고, Container의 세로 길이에 넣어줬다.
두 번째 자식 Expanded는 나머지 공간을 가득 채운다. 해당 공간에 flex
로 비율을 조절한 Expanded 위젯을 넣어 각각 보라색과 회색으로 칠해줬다.
두 번째 자식은 위에서 끝났으니, 이제 첫 번째 자식 컨테이너를 어떻게 네 개로 나눌지 생각해 봐야 한다. 이때 사용한 것이 바로 GridView 위젯이다.
GridView 위젯은 css의 display: grid
와 비슷한 것 같다. 스크롤 되는 방향을 주축으로 하여 그리드를 나눠준다. 가장 많이 사용되는 것은 GridView.count 생성자와 GridView.extent 생성자다. GridView.count는 교차 축의 타일 수가 고정된 레이아웃을 제공하고, GridView.extent는 교차 축의 최대 타일 범위가 있는 레이아웃을 제공한다.
GridView.count(
crossAxisSpacing: 5,
mainAxisSpacing: 5,
crossAxisCount: 2,
children: [
Container(color: Colors.green),
Container(color: Colors.red),
Container(color: Colors.orange),
Container(color: Colors.blue),
],
),
위에서 만든 첫 번째 자식 컨테이너의 자식으로 이런 GridView 위젯을 넣어줬다. crossAxisCount
는 2개의 열로 레이아웃을 만들겠다는 것이고, crossAxisSpacing
, mainAxisSpacing
은 해당 축에서의 자식 간의 간격을 의미한다. 부모 Container의 색을 Colors.black
으로 주어서 마치 검은색 테두리가 있는 것처럼 보이도록 했다.
오늘의 심화 문제는 머리와 몸통의 사이즈를 조정할 수 있는 아바타를 만드는 것이다. 사이즈를 조정하기 위해서 특정 범위의 값을 선택하기 위해 사용되는 Slider 위젯을 썼다.
이렇게 생긴 친구다. 위젯의 value
속성은 현제 thumb의 위치를 나타내며, min
, max
속성은 track의 최대, 최솟값을 나타낸다. onChanged
속성에는 Slider의 값이 바뀌었을 때 호출할 함수를 지정한다.
build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildAvatar(),
const SizedBox(height: 50),
Slider(
value: bigHead,
max: 100,
onChanged: changeHeadSize,
),
const SizedBox(height: 20),
Slider(
value: roundedShoulder,
max: 100,
onChanged: changeBodySize,
),
],
),
),
),
);
}
Widget
이렇게 슬라이더를 두 개 넣고, onChanged
함수에서 setState()
를 통해 값을 업데이트 해줬다.
오늘 문제는 그렇게 어렵지 않았다. 약간 쉬어가는 느낌이었달까~ 레이아웃을 짤 때마다 "맞겠지...?"라는 마음이었는데, 오늘 좀 연습을 해보니 감이 잡힌다. 내일도 화이팅이다~!
https://api.flutter.dev/flutter/widgets/MediaQuery-class.html
https://api.flutter.dev/flutter/widgets/MediaQueryData-class.html