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-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를 다시 짚고 넘어갈 수 있는 기회였던것같습니다. 코드랩에 설명히 자세하기 나와 있어 별 어려움없이 진행할 수 있었습니다.
실습 코드