본문 바로가기

개발/Study

[GoToLearn] Flutter CodeLab - MDC-102 Flutter:Material Structure and Layout

GoToLearn 3주차

GoToLearn 3주차 CodeLab과제인  MDC-102 Flutter:Material Structure and Layout을 진행해보겠습니다. 제목을 보니 머터리얼의 구조와 레이아웃에 대해 학습하는 것 같습니다.

 

MDC-102 Flutter:Material Structure and Layout

https://codelabs.developers.google.com/codelabs/mdc-102-flutter?hl=en#0

 

MDC-102 Flutter: 머티리얼 구조 및 레이아웃  |  Google Codelabs

Material을 사용하여 Flutter 앱의 구조와 레이아웃을 만드는 방법을 알아봅니다.

codelabs.developers.google.com

 

소개

이번 코드랩에서 빌드할 항목

이번 코드랩에서는 MDC-101과 이어지는 코드랩입니다. Shrine이라는 전자상거래 앱의 AppBar와 상품의 GridView를 구현해봅니다.

사용하는 머터리얼 컴포넌트 및 하위 위젯

  • Top app bar
  • Grids
  • Cards

Flutter 환경 설정 및 Starter App

환경 설정은 이미 완료되어 패스하고, 스타터 앱은 전 주에 진행한 코드랩에 이어서 진행하도록 하겠습니다.


상단 앱 바 추가

AppBar 위젯 추가

이전 로그인 화면에서 Next 버튼을 선택했을때 나오는 HomePage 위젯에 AppBar를 추가해줍니다. AppBar를 추가했는데 표시가 되지 않는다면 body의 색과 appbar의 색이 같은지 의심해봐야합니다. AppBar의 색상은 backgroundColor 파라미터를 통해 지정해줄 수 있습니다.

return Scaffold(
  appBar: AppBar(
  ),
);

Text 위젯 추가

AppBar의 title을 Text 위젯을 추가해줍니다. Android에서는 AppBar의 Title 왼쪽으로 정렬되고, iOS에서는 중앙정렬이 됩니다. 플랫폼마다 편차가 배치되는 기준이 다를 수 있음을 고려해야합니다. (저는 iOS Simulator로 하니 중앙정렬로 표시되는것이 확인됩니다.)

 

return Scaffold(
  appBar: AppBar(
    title: const Text('SHRINE'),
  ),
);

 

Leading IconButton 위젯 추가

AppBar leading 필드에 IconButton을 추가합니다. 아이콘 버튼이 타이틀 앞에 배치되는 것을 확인할 수 있습니다.
사용할 수 있는 머터리얼 Icon 홈페이지(https://fonts.google.com/icons)에서 확인할 수 있습니다.

return Scaffold(
  appBar: AppBar(
  leading: IconButton(
    onPressed: () {
      print('Menu button');
    },
    icon: const Icon(
    Icons.menu,
    semanticLabel: 'menu',
    ),
  ),
    title: const Text('SHRINE'),
  ),
);

Actions 추가

Titile 우측에 오는 Actions 버튼을 추가합니다. Actions가 들어가면서 title이 왼쪽 정렬로 변경되는 것을 확인할 수 있습니다.

return Scaffold(
  appBar: AppBar(
    leading: IconButton(
      onPressed: () {
        print('Menu button');
      },
      icon: const Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
    ),
    title: const Text('SHRINE'),
    actions: [
      IconButton(
        onPressed: () {
          print('Search button');
        },
        icon: const Icon(
          Icons.search,
          semanticLabel: 'search',
        ),
      ),
      IconButton(
        onPressed: () {
          print('Filter button');
        },
        icon: const Icon(
          Icons.tune,
          semanticLabel: 'filter',
        ),
       ),
    ],
  ),
);

 


Grid에 Card 추가

GridView 추가

body에 GridView로 교체해줍니다. 필드에 대한 설명은 주석으로 처리하겠습니다.

body: GridView.count(
  // 한 줄에 표시될 갯수
  crossAxisCount: 2,
  // 패딩
  padding: const EdgeInsets.all(16.0),
  // 항목의 가로/세로 비율
  childAspectRatio: 8.0 / 9.0,
  children: [
    Card(),
  ],
),

카드 내용 추가

카드에 레이아웃에 이미지, 제목, 보조 텍스트에 대한 영역을 추가합니다.

Card(
  // 부드러운 곡선 표시
  clipBehavior: Clip.antiAlias,
  child: Column(
    // Column의 배치 방향
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      AspectRatio(
        aspectRatio: 18.0 / 11.0,
        child: Image.asset('assets/diamond.png'),
      ),
      Padding(
        padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text('Title'),
            const SizedBox(height: 8.0),
            Text('Secondary Text'),
          ],
        ),
      ),
    ],
  ),
),

Card 컬렉션 만들기

여러개의 카드를 반환하는 함수 만들기

만들 개수를 입력받아 개수만큼의 카드를 반환하는 함수를 만들어줍니다. 만들고 난 뒤 GridView의 children에 넣어줍니다.

  List<Card> _buildGridCards(int count) {
    List<Card> cards = List.generate(
      count,
      (index) => Card(
        // 부드러운 곡선 표시
        clipBehavior: Clip.antiAlias,
        child: Column(
          // Column의 배치 방향
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            AspectRatio(
              aspectRatio: 18.0 / 11.0,
              child: Image.asset('assets/diamond.png'),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text('Title'),
                  const SizedBox(height: 8.0),
                  Text('Secondary Text'),
                ],
              ),
            ),
          ],
        ),
      ),
    );

    return cards;
  }

카드 위젯에 제품 데이터 추가

위에 작성한 _buildGridCards 함수를 변경해줍니다. model 패키지에 있는 product와 product_repository를 사용해 데이터를 가져온 뒤 데이터에 맞게 카드 리스트를 반환하는 함수로 변경합니다. 또한, 카드 내에 들어가는 이미지는 기본적으로 scaleDown으로 그려지기 때문에 해당 카드의 폭을 다 사용하기 위해 BoxFit.fitWidth를 사용해줍니다.

  List<Card> _buildGridCards(BuildContext context) {
    List<Product> products = ProductsRepository.loadProducts(Category.all);

    if (products.isEmpty) {
      return const <Card>[];
    }

    final ThemeData theme = Theme.of(context);
    final NumberFormat formatter = NumberFormat.simpleCurrency(
        locale: Localizations.localeOf(context).toString());

    return products.map((product) {
      return Card(
        clipBehavior: Clip.antiAlias,
        // TODO: Adjust card heights (103)
        child: Column(
          // TODO: Center items on the card (103)
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            AspectRatio(
              aspectRatio: 18 / 11,
              child: Image.asset(
                product.assetName,
                package: product.assetPackage,
                fit: BoxFit.fitWidth,
              ),
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
                child: Column(
                  // TODO: Align labels to the bottom and center (103)
                  crossAxisAlignment: CrossAxisAlignment.start,
                  // TODO: Change innermost Column (103)
                  children: <Widget>[
                    // TODO: Handle overflowing labels (103)
                    Text(
                      product.name,
                      style: theme.textTheme.titleLarge,
                      maxLines: 1,
                    ),
                    const SizedBox(height: 8.0),
                    Text(
                      formatter.format(product.price),
                      style: theme.textTheme.titleSmall,
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      );
    }).toList();
  }

완성

코드랩을 완성하면 아래와 같은 결과물을 얻을 수 있습니다.

로그인 이후 홈페이지 화면에서 상품을 표시해주는 앱을 완성할 수 있습니다.


후기

이번 코드랩에서는 자주 사용하는 Card, GridView, AppBar를 다시 짚고 넘어갈 수 있는 기회였던것같습니다. 코드랩에 설명히 자세하기 나와 있어 별 어려움없이 진행할 수 있었습니다.

실습 코드

https://github.com/wonyong-park/flutter_codelabs/tree/main/material-components-flutter-codelabs-101-starter

 

flutter_codelabs/material-components-flutter-codelabs-101-starter at main · wonyong-park/flutter_codelabs

flutter_codelabs_repo. Contribute to wonyong-park/flutter_codelabs development by creating an account on GitHub.

github.com