[FlutterBoot] Day03 - 레이아웃 마스터

tamagoyakii·2023년 12월 2일
0

FlutterBoot

목록 보기
5/6
post-thumbnail

✨ 문제 풀이

✅ 기본 문제 - HelloLayout

✅ 심화 문제 - MySlackAvatar

✨ TIL

1. MediaQuery



오늘의 기본 문제는 이런 레이아웃을 만들어보는 문제였다. 화면 사이즈에 영향을 받는 레이아웃이었는데, 위에 4등분 되어있는 정사각형은 항상 사이즈를 유지하고 있다.

이를 구현하려면 디스플레이의 가로 길이를 알아야 한다고 생각했다. 그러기 위해 사용한 MediaQuery!

MediaQueryData

MediaQueryData는 미디어에 대한 정보다. 현재 미디어의 창에 대한 정보를 담고 있다. 주어진 BuildContext에 대한 미디어 쿼리 데이터를 받기 위해서 사용되는 것이 바로 MediaQuery.of 함수다.

만약에 범위 내에 MediaQuery가 없다면, MediaQuery.of 메서드는 예외를 throw 한다. MediaQuery.maybeOf 메서드로 예외 대신 null을 받는 방법도 있다. MediaQuery에는 굉장히 다양한 메서드가 있는데, maybe~가 붙는 메서드들은 모두 일치하는 데이터가 없다면 예외 대신 null을 리턴하는 메서드들이다.

MediaQuery & MediaQuery.of

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 위젯을 넣어 각각 보라색과 회색으로 칠해줬다.

2. GridView

두 번째 자식은 위에서 끝났으니, 이제 첫 번째 자식 컨테이너를 어떻게 네 개로 나눌지 생각해 봐야 한다. 이때 사용한 것이 바로 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으로 주어서 마치 검은색 테두리가 있는 것처럼 보이도록 했다.

3. Slider


오늘의 심화 문제는 머리와 몸통의 사이즈를 조정할 수 있는 아바타를 만드는 것이다. 사이즈를 조정하기 위해서 특정 범위의 값을 선택하기 위해 사용되는 Slider 위젯을 썼다.

이렇게 생긴 친구다. 위젯의 value 속성은 현제 thumb의 위치를 나타내며, min, max 속성은 track의 최대, 최솟값을 나타낸다. onChanged 속성에는 Slider의 값이 바뀌었을 때 호출할 함수를 지정한다.


Widget 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,
            ),
          ],
        ),
      ),
    ),
  );
}

이렇게 슬라이더를 두 개 넣고, onChanged 함수에서 setState()를 통해 값을 업데이트 해줬다.

✨ Comment

오늘 문제는 그렇게 어렵지 않았다. 약간 쉬어가는 느낌이었달까~ 레이아웃을 짤 때마다 "맞겠지...?"라는 마음이었는데, 오늘 좀 연습을 해보니 감이 잡힌다. 내일도 화이팅이다~!

참고

https://api.flutter.dev/flutter/widgets/MediaQuery-class.html
https://api.flutter.dev/flutter/widgets/MediaQueryData-class.html

0개의 댓글